Source code
Revision control
Copy as Markdown
Other Tools
/*
* 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
*/
#ifndef GRAPH_PAIRPOS_GRAPH_HH
#define GRAPH_PAIRPOS_GRAPH_HH
#include "split-helpers.hh"
#include "coverage-graph.hh"
#include "classdef-graph.hh"
#include "../OT/Layout/GPOS/PairPos.hh"
#include "../OT/Layout/GPOS/PosLookupSubTable.hh"
namespace graph {
struct PairPosFormat1 : public OT::Layout::GPOS_impl::PairPosFormat1_3<SmallTypes>
{
bool sanitize (graph_t::vertex_t& vertex) const
{
int64_t vertex_len = vertex.obj.tail - vertex.obj.head;
unsigned min_size = OT::Layout::GPOS_impl::PairPosFormat1_3<SmallTypes>::min_size;
if (vertex_len < min_size) return false;
hb_barrier ();
return vertex_len >=
min_size + pairSet.get_size () - pairSet.len.get_size();
}
hb_vector_t<unsigned> split_subtables (gsubgpos_graph_context_t& c,
unsigned parent_index,
unsigned this_index)
{
hb_set_t visited;
const unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage);
const unsigned coverage_size = c.graph.vertices_[coverage_id].table_size ();
const unsigned base_size = OT::Layout::GPOS_impl::PairPosFormat1_3<SmallTypes>::min_size;
unsigned partial_coverage_size = 4;
unsigned accumulated = base_size;
hb_vector_t<unsigned> split_points;
for (unsigned i = 0; i < pairSet.len; i++)
{
unsigned pair_set_index = pair_set_graph_index (c, this_index, i);
unsigned accumulated_delta =
c.graph.find_subgraph_size (pair_set_index, visited) +
SmallTypes::size; // for PairSet offset.
partial_coverage_size += OT::HBUINT16::static_size;
accumulated += accumulated_delta;
unsigned total = accumulated + hb_min (partial_coverage_size, coverage_size);
if (total >= (1 << 16))
{
split_points.push (i);
accumulated = base_size + accumulated_delta;
partial_coverage_size = 6;
visited.clear (); // node sharing isn't allowed between splits.
}
}
split_context_t split_context {
c,
this,
c.graph.duplicate_if_shared (parent_index, this_index),
};
return actuate_subtable_split<split_context_t> (split_context, split_points);
}
private:
struct split_context_t {
gsubgpos_graph_context_t& c;
PairPosFormat1* thiz;
unsigned this_index;
unsigned original_count ()
{
return thiz->pairSet.len;
}
unsigned clone_range (unsigned start, unsigned end)
{
return thiz->clone_range (this->c, this->this_index, start, end);
}
bool shrink (unsigned count)
{
return thiz->shrink (this->c, this->this_index, count);
}
};
bool shrink (gsubgpos_graph_context_t& c,
unsigned this_index,
unsigned count)
{
DEBUG_MSG (SUBSET_REPACK, nullptr,
" Shrinking PairPosFormat1 (%u) to [0, %u).",
this_index,
count);
unsigned old_count = pairSet.len;
if (count >= old_count)
return true;
pairSet.len = count;
c.graph.vertices_[this_index].obj.tail -= (old_count - count) * SmallTypes::size;
auto coverage = c.graph.as_mutable_table<Coverage> (this_index, &this->coverage);
if (!coverage) return false;
unsigned coverage_size = coverage.vertex->table_size ();
auto new_coverage =
+ hb_zip (coverage.table->iter (), hb_range ())
| hb_filter ([&] (hb_pair_t<unsigned, unsigned> p) {
return p.second < count;
})
| hb_map_retains_sorting (hb_first)
;
return Coverage::make_coverage (c, new_coverage, coverage.index, coverage_size);
}
// Create a new PairPos including PairSet's from start (inclusive) to end (exclusive).
// Returns object id of the new object.
unsigned clone_range (gsubgpos_graph_context_t& c,
unsigned this_index,
unsigned start, unsigned end) const
{
DEBUG_MSG (SUBSET_REPACK, nullptr,
" Cloning PairPosFormat1 (%u) range [%u, %u).", this_index, start, end);
unsigned num_pair_sets = end - start;
unsigned prime_size = OT::Layout::GPOS_impl::PairPosFormat1_3<SmallTypes>::min_size
+ num_pair_sets * SmallTypes::size;
unsigned pair_pos_prime_id = c.create_node (prime_size);
if (pair_pos_prime_id == (unsigned) -1) return -1;
PairPosFormat1* pair_pos_prime = (PairPosFormat1*) c.graph.object (pair_pos_prime_id).head;
pair_pos_prime->format = this->format;
pair_pos_prime->valueFormat[0] = this->valueFormat[0];
pair_pos_prime->valueFormat[1] = this->valueFormat[1];
pair_pos_prime->pairSet.len = num_pair_sets;
for (unsigned i = start; i < end; i++)
{
c.graph.move_child<> (this_index,
&pairSet[i],
pair_pos_prime_id,
&pair_pos_prime->pairSet[i - start]);
}
unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage);
if (!Coverage::clone_coverage (c,
coverage_id,
pair_pos_prime_id,
2,
start, end))
return -1;
return pair_pos_prime_id;
}
unsigned pair_set_graph_index (gsubgpos_graph_context_t& c, unsigned this_index, unsigned i) const
{
return c.graph.index_for_offset (this_index, &pairSet[i]);
}
};
struct PairPosFormat2 : public OT::Layout::GPOS_impl::PairPosFormat2_4<SmallTypes>
{
bool sanitize (graph_t::vertex_t& vertex) const
{
size_t vertex_len = vertex.table_size ();
unsigned min_size = OT::Layout::GPOS_impl::PairPosFormat2_4<SmallTypes>::min_size;
if (vertex_len < min_size) return false;
hb_barrier ();
const unsigned class1_count = class1Count;
return vertex_len >=
min_size + class1_count * get_class1_record_size ();
}
hb_vector_t<unsigned> split_subtables (gsubgpos_graph_context_t& c,
unsigned parent_index,
unsigned this_index)
{
const unsigned base_size = OT::Layout::GPOS_impl::PairPosFormat2_4<SmallTypes>::min_size;
const unsigned class_def_2_size = size_of (c, this_index, &classDef2);
const Coverage* coverage = get_coverage (c, this_index);
const ClassDef* class_def_1 = get_class_def_1 (c, this_index);
auto gid_and_class =
+ coverage->iter ()
| hb_map_retains_sorting ([&] (hb_codepoint_t gid) {
return hb_codepoint_pair_t (gid, class_def_1->get_class (gid));
})
;
class_def_size_estimator_t estimator (gid_and_class);
const unsigned class1_count = class1Count;
const unsigned class2_count = class2Count;
const unsigned class1_record_size = get_class1_record_size ();
const unsigned value_1_len = valueFormat1.get_len ();
const unsigned value_2_len = valueFormat2.get_len ();
const unsigned total_value_len = value_1_len + value_2_len;
unsigned accumulated = base_size;
unsigned coverage_size = 4;
unsigned class_def_1_size = 4;
unsigned max_coverage_size = coverage_size;
unsigned max_class_def_1_size = class_def_1_size;
hb_vector_t<unsigned> split_points;
hb_hashmap_t<unsigned, unsigned> device_tables = get_all_device_tables (c, this_index);
hb_vector_t<unsigned> format1_device_table_indices = valueFormat1.get_device_table_indices ();
hb_vector_t<unsigned> format2_device_table_indices = valueFormat2.get_device_table_indices ();
bool has_device_tables = bool(format1_device_table_indices) || bool(format2_device_table_indices);
hb_set_t visited;
for (unsigned i = 0; i < class1_count; i++)
{
unsigned accumulated_delta = class1_record_size;
class_def_1_size = estimator.add_class_def_size (i);
coverage_size = estimator.coverage_size ();
max_coverage_size = hb_max (max_coverage_size, coverage_size);
max_class_def_1_size = hb_max (max_class_def_1_size, class_def_1_size);
if (has_device_tables) {
for (unsigned j = 0; j < class2_count; j++)
{
unsigned value1_index = total_value_len * (class2_count * i + j);
unsigned value2_index = value1_index + value_1_len;
accumulated_delta += size_of_value_record_children (c,
device_tables,
format1_device_table_indices,
value1_index,
visited);
accumulated_delta += size_of_value_record_children (c,
device_tables,
format2_device_table_indices,
value2_index,
visited);
}
}
accumulated += accumulated_delta;
unsigned total = accumulated
+ coverage_size + class_def_1_size + class_def_2_size
// The largest object will pack last and can exceed the size limit.
- hb_max (hb_max (coverage_size, class_def_1_size), class_def_2_size);
if (total >= (1 << 16))
{
split_points.push (i);
// split does not include i, so add the size for i when we reset the size counters.
accumulated = base_size + accumulated_delta;
estimator.reset();
class_def_1_size = estimator.add_class_def_size(i);
coverage_size = estimator.coverage_size();
visited.clear (); // node sharing isn't allowed between splits.
}
}
split_context_t split_context {
c,
this,
c.graph.duplicate_if_shared (parent_index, this_index),
class1_record_size,
total_value_len,
value_1_len,
value_2_len,
max_coverage_size,
max_class_def_1_size,
device_tables,
format1_device_table_indices,
format2_device_table_indices
};
return actuate_subtable_split<split_context_t> (split_context, split_points);
}
private:
struct split_context_t
{
gsubgpos_graph_context_t& c;
PairPosFormat2* thiz;
unsigned this_index;
unsigned class1_record_size;
unsigned value_record_len;
unsigned value1_record_len;
unsigned value2_record_len;
unsigned max_coverage_size;
unsigned max_class_def_size;
const hb_hashmap_t<unsigned, unsigned>& device_tables;
const hb_vector_t<unsigned>& format1_device_table_indices;
const hb_vector_t<unsigned>& format2_device_table_indices;
unsigned original_count ()
{
return thiz->class1Count;
}
unsigned clone_range (unsigned start, unsigned end)
{
return thiz->clone_range (*this, start, end);
}
bool shrink (unsigned count)
{
return thiz->shrink (*this, count);
}
};
size_t get_class1_record_size () const
{
const size_t class2_count = class2Count;
return
class2_count * (valueFormat1.get_size () + valueFormat2.get_size ());
}
unsigned clone_range (split_context_t& split_context,
unsigned start, unsigned end) const
{
DEBUG_MSG (SUBSET_REPACK, nullptr,
" Cloning PairPosFormat2 (%u) range [%u, %u).", split_context.this_index, start, end);
graph_t& graph = split_context.c.graph;
unsigned num_records = end - start;
unsigned prime_size = OT::Layout::GPOS_impl::PairPosFormat2_4<SmallTypes>::min_size
+ num_records * split_context.class1_record_size;
unsigned pair_pos_prime_id = split_context.c.create_node (prime_size);
if (pair_pos_prime_id == (unsigned) -1) return -1;
PairPosFormat2* pair_pos_prime =
(PairPosFormat2*) graph.object (pair_pos_prime_id).head;
pair_pos_prime->format = this->format;
pair_pos_prime->valueFormat1 = this->valueFormat1;
pair_pos_prime->valueFormat2 = this->valueFormat2;
pair_pos_prime->class1Count = num_records;
pair_pos_prime->class2Count = this->class2Count;
clone_class1_records (split_context,
pair_pos_prime_id,
start,
end);
unsigned coverage_id =
graph.index_for_offset (split_context.this_index, &coverage);
unsigned class_def_1_id =
graph.index_for_offset (split_context.this_index, &classDef1);
auto& coverage_v = graph.vertices_[coverage_id];
auto& class_def_1_v = graph.vertices_[class_def_1_id];
Coverage* coverage_table = (Coverage*) coverage_v.obj.head;
ClassDef* class_def_1_table = (ClassDef*) class_def_1_v.obj.head;
if (!coverage_table
|| !coverage_table->sanitize (coverage_v)
|| !class_def_1_table
|| !class_def_1_table->sanitize (class_def_1_v))
return -1;
auto klass_map =
+ coverage_table->iter ()
| hb_map_retains_sorting ([&] (hb_codepoint_t gid) {
return hb_codepoint_pair_t (gid, class_def_1_table->get_class (gid));
})
| hb_filter ([&] (hb_codepoint_t klass) {
return klass >= start && klass < end;
}, hb_second)
| hb_map_retains_sorting ([&] (hb_codepoint_pair_t gid_and_class) {
// Classes must be from 0...N so subtract start
return hb_codepoint_pair_t (gid_and_class.first, gid_and_class.second - start);
})
;
if (!Coverage::add_coverage (split_context.c,
pair_pos_prime_id,
2,
+ klass_map | hb_map_retains_sorting (hb_first),
split_context.max_coverage_size))
return -1;
// classDef1
if (!ClassDef::add_class_def (split_context.c,
pair_pos_prime_id,
8,
+ klass_map,
split_context.max_class_def_size))
return -1;
// classDef2
unsigned class_def_2_id =
graph.index_for_offset (split_context.this_index, &classDef2);
auto* class_def_link = graph.vertices_[pair_pos_prime_id].obj.real_links.push ();
class_def_link->width = SmallTypes::size;
class_def_link->objidx = class_def_2_id;
class_def_link->position = 10;
graph.vertices_[class_def_2_id].add_parent (pair_pos_prime_id);
graph.duplicate (pair_pos_prime_id, class_def_2_id);
return pair_pos_prime_id;
}
void clone_class1_records (split_context_t& split_context,
unsigned pair_pos_prime_id,
unsigned start, unsigned end) const
{
PairPosFormat2* pair_pos_prime =
(PairPosFormat2*) split_context.c.graph.object (pair_pos_prime_id).head;
char* start_addr = ((char*)&values[0]) + start * split_context.class1_record_size;
unsigned num_records = end - start;
hb_memcpy (&pair_pos_prime->values[0],
start_addr,
num_records * split_context.class1_record_size);
if (!split_context.format1_device_table_indices
&& !split_context.format2_device_table_indices)
// No device tables to move over.
return;
unsigned class2_count = class2Count;
for (unsigned i = start; i < end; i++)
{
for (unsigned j = 0; j < class2_count; j++)
{
unsigned value1_index = split_context.value_record_len * (class2_count * i + j);
unsigned value2_index = value1_index + split_context.value1_record_len;
unsigned new_value1_index = split_context.value_record_len * (class2_count * (i - start) + j);
unsigned new_value2_index = new_value1_index + split_context.value1_record_len;
transfer_device_tables (split_context,
pair_pos_prime_id,
split_context.format1_device_table_indices,
value1_index,
new_value1_index);
transfer_device_tables (split_context,
pair_pos_prime_id,
split_context.format2_device_table_indices,
value2_index,
new_value2_index);
}
}
}
void transfer_device_tables (split_context_t& split_context,
unsigned pair_pos_prime_id,
const hb_vector_t<unsigned>& device_table_indices,
unsigned old_value_record_index,
unsigned new_value_record_index) const
{
PairPosFormat2* pair_pos_prime =
(PairPosFormat2*) split_context.c.graph.object (pair_pos_prime_id).head;
for (unsigned i : device_table_indices)
{
OT::Offset16* record = (OT::Offset16*) &values[old_value_record_index + i];
unsigned record_position = ((char*) record) - ((char*) this);
if (!split_context.device_tables.has (record_position)) continue;
split_context.c.graph.move_child (
split_context.this_index,
record,
pair_pos_prime_id,
(OT::Offset16*) &pair_pos_prime->values[new_value_record_index + i]);
}
}
bool shrink (split_context_t& split_context,
unsigned count)
{
DEBUG_MSG (SUBSET_REPACK, nullptr,
" Shrinking PairPosFormat2 (%u) to [0, %u).",
split_context.this_index,
count);
unsigned old_count = class1Count;
if (count >= old_count)
return true;
graph_t& graph = split_context.c.graph;
class1Count = count;
graph.vertices_[split_context.this_index].obj.tail -=
(old_count - count) * split_context.class1_record_size;
auto coverage =
graph.as_mutable_table<Coverage> (split_context.this_index, &this->coverage);
if (!coverage) return false;
auto class_def_1 =
graph.as_mutable_table<ClassDef> (split_context.this_index, &classDef1);
if (!class_def_1) return false;
auto klass_map =
+ coverage.table->iter ()
| hb_map_retains_sorting ([&] (hb_codepoint_t gid) {
return hb_codepoint_pair_t (gid, class_def_1.table->get_class (gid));
})
| hb_filter ([&] (hb_codepoint_t klass) {
return klass < count;
}, hb_second)
;
auto new_coverage = + klass_map | hb_map_retains_sorting (hb_first);
if (!Coverage::make_coverage (split_context.c,
+ new_coverage,
coverage.index,
// existing ranges my not be kept, worst case size is a format 1
// coverage table.
4 + new_coverage.len() * 2))
return false;
return ClassDef::make_class_def (split_context.c,
+ klass_map,
class_def_1.index,
class_def_1.vertex->table_size ());
}
hb_hashmap_t<unsigned, unsigned>
get_all_device_tables (gsubgpos_graph_context_t& c,
unsigned this_index) const
{
const auto& v = c.graph.vertices_[this_index];
return v.position_to_index_map ();
}
const Coverage* get_coverage (gsubgpos_graph_context_t& c,
unsigned this_index) const
{
unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage);
auto& coverage_v = c.graph.vertices_[coverage_id];
Coverage* coverage_table = (Coverage*) coverage_v.obj.head;
if (!coverage_table || !coverage_table->sanitize (coverage_v))
return &Null(Coverage);
return coverage_table;
}
const ClassDef* get_class_def_1 (gsubgpos_graph_context_t& c,
unsigned this_index) const
{
unsigned class_def_1_id = c.graph.index_for_offset (this_index, &classDef1);
auto& class_def_1_v = c.graph.vertices_[class_def_1_id];
ClassDef* class_def_1_table = (ClassDef*) class_def_1_v.obj.head;
if (!class_def_1_table || !class_def_1_table->sanitize (class_def_1_v))
return &Null(ClassDef);
return class_def_1_table;
}
unsigned size_of_value_record_children (gsubgpos_graph_context_t& c,
const hb_hashmap_t<unsigned, unsigned>& device_tables,
const hb_vector_t<unsigned> device_table_indices,
unsigned value_record_index,
hb_set_t& visited)
{
unsigned size = 0;
for (unsigned i : device_table_indices)
{
OT::Layout::GPOS_impl::Value* record = &values[value_record_index + i];
unsigned record_position = ((char*) record) - ((char*) this);
unsigned* obj_idx;
if (!device_tables.has (record_position, &obj_idx)) continue;
size += c.graph.find_subgraph_size (*obj_idx, visited);
}
return size;
}
unsigned size_of (gsubgpos_graph_context_t& c,
unsigned this_index,
const void* offset) const
{
const unsigned id = c.graph.index_for_offset (this_index, offset);
return c.graph.vertices_[id].table_size ();
}
};
struct PairPos : public OT::Layout::GPOS_impl::PairPos
{
hb_vector_t<unsigned> split_subtables (gsubgpos_graph_context_t& c,
unsigned parent_index,
unsigned this_index)
{
switch (u.format) {
case 1:
return ((PairPosFormat1*)(&u.format1))->split_subtables (c, parent_index, this_index);
case 2:
return ((PairPosFormat2*)(&u.format2))->split_subtables (c, parent_index, this_index);
#ifndef HB_NO_BEYOND_64K
case 3: HB_FALLTHROUGH;
case 4: HB_FALLTHROUGH;
// Don't split 24bit PairPos's.
#endif
default:
return hb_vector_t<unsigned> ();
}
}
bool sanitize (graph_t::vertex_t& vertex) const
{
int64_t vertex_len = vertex.obj.tail - vertex.obj.head;
if (vertex_len < u.format.get_size ()) return false;
hb_barrier ();
switch (u.format) {
case 1:
return ((PairPosFormat1*)(&u.format1))->sanitize (vertex);
case 2:
return ((PairPosFormat2*)(&u.format2))->sanitize (vertex);
#ifndef HB_NO_BEYOND_64K
case 3: HB_FALLTHROUGH;
case 4: HB_FALLTHROUGH;
#endif
default:
// We don't handle format 3 and 4 here.
return false;
}
}
};
}
#endif // GRAPH_PAIRPOS_GRAPH_HH