blob: 044ddf3317c45d629c246435ed3dd212cc83d630 [file]
/*
* Copyright © 2022 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Garret Rieger
*/
#include "graph.hh"
#include "../hb-ot-layout-gsubgpos.hh"
#include "../OT/Layout/GSUB/ExtensionSubst.hh"
#include "../OT/Layout/GSUB/SubstLookupSubTable.hh"
#include "gsubgpos-context.hh"
#include "pairpos-graph.hh"
#include "markbasepos-graph.hh"
#include "ligature-graph.hh"
#ifndef GRAPH_GSUBGPOS_GRAPH_HH
#define GRAPH_GSUBGPOS_GRAPH_HH
namespace graph {
struct Lookup;
template<typename T>
struct ExtensionFormat1 : public OT::ExtensionFormat1<T>
{
void reset(unsigned type)
{
this->format = 1;
this->extensionLookupType = type;
this->extensionOffset = 0;
}
graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
{
size_t vertex_len = vertex.table_size ();
if (unlikely (vertex_len < OT::ExtensionFormat1<T>::static_size))
return Err(SANITIZE_FAILURE);
return Ok();
}
unsigned get_lookup_type () const
{
return this->extensionLookupType;
}
graph_result_t<unsigned> get_subtable_index (graph_t& graph, unsigned this_index) const
{
return graph.index_for_offset (this_index, &this->extensionOffset);
}
};
struct Lookup : public OT::Lookup
{
unsigned number_of_subtables () const
{
return subTable.len;
}
graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
{
size_t vertex_len = vertex.table_size ();
if (unlikely (vertex_len < OT::Lookup::min_size)) return Err(SANITIZE_FAILURE);
hb_barrier ();
if (unlikely (vertex_len < this->get_size ())) return Err(SANITIZE_FAILURE);
return Ok();
}
bool is_extension (hb_tag_t table_tag) const
{
return lookupType == extension_type (table_tag);
}
bool use_mark_filtering_set () const
{
unsigned flag = lookupFlag;
return flag & 0x0010u;
}
graph_result_t<void> make_extension (gsubgpos_graph_context_t& c,
unsigned this_index)
{
unsigned type = lookupType;
unsigned ext_type = extension_type (c.table_tag);
if (!ext_type || is_extension (c.table_tag))
{
// NOOP
return Ok();
}
DEBUG_MSG (SUBSET_REPACK, nullptr,
"Promoting lookup type %u (obj %u) to extension.",
type,
this_index);
hb_set_t seen;
hb_vector_t<unsigned> subtables;
for (unsigned i = 0; i < subTable.len; i++)
{
TRY_ASSIGN (unsigned subtable_index, c.graph.index_for_offset (this_index, &subTable[i]));
if (seen.has (subtable_index))
continue;
seen.add (subtable_index);
subtables.push (subtable_index);
}
TRY (graph_result_t<void>::from (seen, ALLOCATION_FAILURE));
TRY (graph_result_t<void>::from (subtables, ALLOCATION_FAILURE));
for (unsigned subtable_index : subtables)
TRY (make_subtable_extension (c, this_index, subtable_index));
lookupType = ext_type;
return Ok();
}
graph_result_t<void> split_subtables_if_needed (gsubgpos_graph_context_t& c,
unsigned this_index)
{
unsigned type = lookupType;
bool is_ext = is_extension (c.table_tag);
if (c.table_tag != HB_OT_TAG_GPOS && c.table_tag != HB_OT_TAG_GSUB)
return Ok();
if (!is_ext && !is_supported_gpos_type(type, c) && !is_supported_gsub_type(type, c))
return Ok();
hb_vector_t<hb_pair_t<unsigned, hb_vector_t<unsigned>>> all_new_subtables;
for (unsigned i = 0; i < subTable.len; i++)
{
// This assumes we are working with something like a GSUB/GPOS table, if
// sanitization fails we ignore and skip splitting for the particular sub table.
auto idx_res = c.graph.index_for_offset (this_index, &subTable[i]);
if (!idx_res.is_ok ()) continue;
unsigned subtable_index = *idx_res;
if (is_ext) {
unsigned ext_subtable_index = subtable_index;
ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>* extension =
(ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>*)
c.graph.object (ext_subtable_index).head;
if (!extension || !extension->sanitize (c.graph.vertices_[ext_subtable_index]).is_ok ())
continue;
TRY_ASSIGN (subtable_index, extension->get_subtable_index (c.graph, ext_subtable_index));
type = extension->get_lookup_type ();
if (!is_supported_gpos_type(type, c) && !is_supported_gsub_type(type, c))
continue;
}
hb_vector_t<unsigned>* split_result;
if (c.split_subtables.has (subtable_index, &split_result))
{
if (split_result->length == 0)
continue;
all_new_subtables.push (hb_pair(i, *split_result));
TRY (graph_result_t<void>::from (all_new_subtables, ALLOCATION_FAILURE));
}
else
{
hb_vector_t<unsigned> new_sub_tables;
if (c.table_tag == HB_OT_TAG_GPOS) {
switch (type)
{
case 2:
{
TRY_ASSIGN (new_sub_tables, split_subtable<PairPos> (c, subtable_index));
break;
}
case 4:
{
TRY_ASSIGN (new_sub_tables, split_subtable<MarkBasePos> (c, subtable_index));
break;
}
default:
break;
}
} else if (c.table_tag == HB_OT_TAG_GSUB) {
switch (type)
{
case 4:
{
TRY_ASSIGN (new_sub_tables, split_subtable<graph::LigatureSubst> (c, subtable_index));
break;
}
default:
break;
}
}
c.split_subtables.set (subtable_index, new_sub_tables);
TRY (graph_result_t<void>::from (c.split_subtables, ALLOCATION_FAILURE));
if (new_sub_tables) {
all_new_subtables.push (hb_pair (i, std::move (new_sub_tables)));
TRY (graph_result_t<void>::from (all_new_subtables, ALLOCATION_FAILURE));
}
}
}
if (all_new_subtables) {
TRY (add_sub_tables (c, this_index, type, all_new_subtables));
}
return Ok();
}
template<typename T>
graph_result_t<hb_vector_t<unsigned>> split_subtable (gsubgpos_graph_context_t& c,
unsigned objidx)
{
T* sub_table = (T*) c.graph.object (objidx).head;
if (!sub_table || !sub_table->sanitize (c.graph.vertices_[objidx]).is_ok ())
return Ok(hb_vector_t<unsigned> ());
return sub_table->split_subtables (c, objidx);
}
graph_result_t<void> add_sub_tables (gsubgpos_graph_context_t& c,
unsigned this_index,
unsigned type,
const hb_vector_t<hb_pair_t<unsigned, hb_vector_t<unsigned>>>& subtable_ids)
{
bool is_ext = is_extension (c.table_tag);
auto* v = &c.graph.vertices_[this_index];
fix_existing_subtable_links (c, this_index, subtable_ids);
unsigned new_subtable_count = 0;
for (const auto& p : subtable_ids)
new_subtable_count += p.second.length;
size_t new_size = v->table_size ()
+ new_subtable_count * OT::Offset16::static_size;
char* buffer = (char*) hb_calloc (1, new_size);
if (unlikely (!buffer)) return Err(ALLOCATION_FAILURE);
auto res = c.add_buffer (buffer);
if (unlikely (!res.is_ok ()))
{
hb_free (buffer);
return res;
}
hb_memcpy (buffer, v->obj ().head, v->table_size());
if (use_mark_filtering_set ())
hb_memcpy (buffer + new_size - 2, v->obj ().tail - 2, 2);
v->set_buffer (buffer, buffer + new_size);
Lookup* new_lookup = (Lookup*) buffer;
unsigned shift = 0;
new_lookup->subTable.len = subTable.len + new_subtable_count;
for (const auto& p : subtable_ids)
{
unsigned offset_index = p.first + shift + 1;
shift += p.second.length;
for (unsigned subtable_id : p.second)
{
if (is_ext)
{
TRY_ASSIGN (unsigned ext_id, create_extension_subtable (c, subtable_id, type));
TRY(c.graph.vertices_[subtable_id].add_parent (ext_id, false));
subtable_id = ext_id;
// the reference to v may have changed on adding a node, so reassign it.
v = &c.graph.vertices_[this_index];
}
TRY(v->add_real_link(2, subtable_id,
(char*) &new_lookup->subTable[offset_index++] - (char*) new_lookup));
TRY(c.graph.vertices_[subtable_id].add_parent (this_index, false));
}
}
// Repacker sort order depends on link order, which we've messed up so resort it.
v->sort_real_links ();
// The head location of the lookup has changed, invalidating the lookups map entry
// in the context. Update the map.
c.lookups.set (this_index, new_lookup);
TRY (graph_result_t<void>::from (c.lookups, ALLOCATION_FAILURE));
return Ok();
}
void fix_existing_subtable_links (gsubgpos_graph_context_t& c,
unsigned this_index,
const hb_vector_t<hb_pair_t<unsigned, hb_vector_t<unsigned>>>& subtable_ids)
{
auto& v = c.graph.vertices_[this_index];
unsigned shift = 0;
for (const auto& p : subtable_ids)
{
unsigned insert_index = p.first + shift;
unsigned pos_offset = p.second.length * OT::Offset16::static_size;
unsigned insert_offset = Lookup::min_size + insert_index * OT::Offset16::static_size;
shift += p.second.length;
for (auto& l : v.all_links_writer ())
{
if (l.position > insert_offset) l.position += pos_offset;
}
}
}
graph_result_t<unsigned> create_extension_subtable (gsubgpos_graph_context_t& c,
unsigned subtable_index,
unsigned type)
{
unsigned extension_size = OT::ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>::static_size;
TRY_ASSIGN (unsigned ext_index, c.create_node (extension_size));
auto& ext_vertex = c.graph.vertices_[ext_index];
ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>* extension =
ext_vertex.as<ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>*> ();
extension->reset (type);
// Make extension point at the subtable.
TRY(ext_vertex.add_real_link (4, subtable_index, 4));
return Ok(ext_index);
}
graph_result_t<void> make_subtable_extension (gsubgpos_graph_context_t& c,
unsigned lookup_index,
unsigned subtable_index)
{
unsigned type = lookupType;
unsigned ext_index = HB_GRAPH_INVALID;
unsigned* existing_ext_index = nullptr;
if (c.subtable_to_extension.has(subtable_index, &existing_ext_index)) {
ext_index = *existing_ext_index;
} else {
TRY_ASSIGN (ext_index, create_extension_subtable(c, subtable_index, type));
c.subtable_to_extension.set(subtable_index, ext_index);
TRY (graph_result_t<void>::from (c.subtable_to_extension, ALLOCATION_FAILURE));
}
auto& subtable_vertex = c.graph.vertices_[subtable_index];
auto& lookup_vertex = c.graph.vertices_[lookup_index];
unsigned replaced = 0;
for (auto& l : lookup_vertex.real_links_writer ())
{
if (l.objidx == subtable_index) {
// Change lookup to point at the extension.
l.objidx = ext_index;
replaced++;
}
}
// Make extension point at the subtable.
auto& ext_vertex = c.graph.vertices_[ext_index];
for (unsigned i = 0; i < replaced; i++)
{
subtable_vertex.remove_parent (lookup_index);
TRY(ext_vertex.add_parent (lookup_index, false));
}
if (!existing_ext_index)
TRY(subtable_vertex.add_parent (ext_index, false));
return Ok();
}
private:
bool is_supported_gsub_type(unsigned type, gsubgpos_graph_context_t& c) const {
return (c.table_tag == HB_OT_TAG_GSUB) && (
type == OT::Layout::GSUB_impl::SubstLookupSubTable::Type::Ligature
);
}
bool is_supported_gpos_type(unsigned type, gsubgpos_graph_context_t& c) const {
return (c.table_tag == HB_OT_TAG_GPOS) && (
type == OT::Layout::GPOS_impl::PosLookupSubTable::Type::Pair ||
type == OT::Layout::GPOS_impl::PosLookupSubTable::Type::MarkBase
);
}
unsigned extension_type (hb_tag_t table_tag) const
{
switch (table_tag)
{
case HB_OT_TAG_GPOS: return 9;
case HB_OT_TAG_GSUB: return 7;
default: return 0;
}
}
};
template <typename T>
struct LookupList : public OT::LookupList<T>
{
graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
{
size_t vertex_len = vertex.table_size ();
if (unlikely (vertex_len < OT::LookupList<T>::min_size)) return Err(SANITIZE_FAILURE);
hb_barrier ();
if (unlikely (vertex_len < OT::LookupList<T>::item_size * this->len)) return Err(SANITIZE_FAILURE);
return Ok();
}
};
struct GSTAR : public OT::GSUBGPOS
{
static graph_result_t<const GSTAR*> graph_to_gstar (graph_t& graph)
{
const auto& r = graph.root ();
const GSTAR* gstar = (GSTAR*) r.obj ().head;
if (unlikely (!gstar)) return Err(INVALID_ARGUMENT);
TRY (gstar->sanitize (r));
hb_barrier ();
return gstar;
}
const void* get_lookup_list_field_offset () const
{
return OT::GSUBGPOS::get_lookup_list_field_offset ();
}
graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
{
size_t len = vertex.table_size ();
if (unlikely (len < OT::GSUBGPOS::min_size)) return Err(SANITIZE_FAILURE);
hb_barrier ();
if (unlikely (version.major != 1)) return Err(SANITIZE_FAILURE);
if (unlikely (len < get_size ())) return Err(SANITIZE_FAILURE);
return Ok();
}
graph_result_t<void> find_lookups (graph_t& graph,
hb_hashmap_t<unsigned, Lookup*>& lookups /* OUT */) const
{
#ifndef HB_NO_BEYOND_64K
if (version.to_int () >= 0x00010002u && lookupList2)
return find_lookups<MediumTypes> (graph, lookups);
#endif
TRY(find_lookups<SmallTypes> (graph, lookups));
return Ok();
}
graph_result_t<unsigned> get_lookup_list_index (graph_t& graph) const
{
const void* offset = get_lookup_list_field_offset ();
if (unlikely (!offset)) return Err(INVALID_ARGUMENT);
return graph.index_for_offset (graph.root_idx (), offset);
}
template<typename Types>
graph_result_t<void> find_lookups (graph_t& graph,
hb_hashmap_t<unsigned, Lookup*>& lookups /* OUT */) const
{
TRY_ASSIGN (unsigned lookup_list_idx, get_lookup_list_index (graph));
const LookupList<Types>* lookupList =
(const LookupList<Types>*) graph.object (lookup_list_idx).head;
if (unlikely (!lookupList)) return Err(INVALID_ARGUMENT);
TRY(lookupList->sanitize (graph.vertices_[lookup_list_idx]));
for (unsigned i = 0; i < lookupList->len; i++)
{
TRY_ASSIGN (unsigned lookup_idx, graph.index_for_offset (lookup_list_idx, &(lookupList->arrayZ[i])));
Lookup* lookup = (Lookup*) graph.object (lookup_idx).head;
if (!lookup || !lookup->sanitize (graph.vertices_[lookup_idx]).is_ok ()) continue;
lookups.set (lookup_idx, lookup);
}
return Ok();
}
};
}
#endif /* GRAPH_GSUBGPOS_GRAPH_HH */