Merge pull request #3410 from harfbuzz/boring-expansion

[Boring Expansion] >64k loca & hmtx tables

This does two things:

The num-glyphs reported by the face now is the maximum reported by the maxp and that deduced from the length of the loca table; I think this is the right thing to do anyway; According to OpenType such loca tables are invalid.

The interpretation hmtx tables that have excessive bytes at the end, again, invalid according to OpenType, has changed. Previously we were interpreting those excessive bytes as extra lsb values. Now we interpret them as extra advance values, the last of which is repeated for all missing glyphs. Again, these are tables that are invalid according to OpenType, and the advances are for glyph indices beyond maxp table's num-glyphs.

The combined effect is that the font can have shapes and advances for gid's beyond the maxp limit of 64k. In fact, maxp table becomes optional.
This commit is contained in:
Behdad Esfahbod 2022-02-16 14:37:09 -06:00 committed by GitHub
commit d2998faad3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 330 additions and 64 deletions

View File

@ -85,6 +85,7 @@
#ifdef HB_MINI
#define HB_NO_AAT
#define HB_NO_LEGACY
#define HB_NO_BORING_EXPANSION
#endif
#if defined(HAVE_CONFIG_OVERRIDE_H) || defined(HB_CONFIG_OVERRIDE_H)

View File

@ -273,14 +273,19 @@ struct hb_face_lazy_loader_t : hb_lazy_loader_t<T,
hb_face_lazy_loader_t<T, WheresFace>,
hb_face_t, WheresFace> {};
template <typename T, unsigned int WheresFace>
template <typename T, unsigned int WheresFace, bool core=false>
struct hb_table_lazy_loader_t : hb_lazy_loader_t<T,
hb_table_lazy_loader_t<T, WheresFace>,
hb_table_lazy_loader_t<T, WheresFace, core>,
hb_face_t, WheresFace,
hb_blob_t>
{
static hb_blob_t *create (hb_face_t *face)
{ return hb_sanitize_context_t ().reference_table<T> (face); }
{
auto c = hb_sanitize_context_t ();
if (core)
c.set_num_glyphs (0); // So we don't recurse ad infinitum...
return c.reference_table<T> (face);
}
static void destroy (hb_blob_t *p) { hb_blob_destroy (p); }
static const hb_blob_t *get_null ()

View File

@ -32,6 +32,11 @@
#define HB_OT_FACE_TABLE_LIST_HH
#endif /* HB_OT_FACE_TABLE_LIST_HH */ /* Dummy header guards */
#ifndef HB_OT_CORE_TABLE
#define HB_OT_CORE_TABLE(Namespace, Type) HB_OT_TABLE (Namespace, Type)
#define _HB_OT_CORE_TABLE_UNDEF
#endif
#ifndef HB_OT_ACCELERATOR
#define HB_OT_ACCELERATOR(Namespace, Type) HB_OT_TABLE (Namespace, Type)
#define _HB_OT_ACCELERATOR_UNDEF
@ -46,7 +51,8 @@
/* OpenType fundamentals. */
HB_OT_TABLE (OT, head)
HB_OT_CORE_TABLE (OT, head)
HB_OT_CORE_TABLE (OT, maxp)
#if !defined(HB_NO_FACE_COLLECT_UNICODES) || !defined(HB_NO_OT_FONT)
HB_OT_ACCELERATOR (OT, cmap)
#endif
@ -74,6 +80,7 @@ HB_OT_TABLE (OT, VORG)
#endif
/* TrueType outlines. */
HB_OT_CORE_TABLE (OT, loca) // Also used to determine number of glyphs
HB_OT_ACCELERATOR (OT, glyf)
/* CFF outlines. */
@ -138,3 +145,7 @@ HB_OT_TABLE (OT, MATH)
#ifdef _HB_OT_ACCELERATOR_UNDEF
#undef HB_OT_ACCELERATOR
#endif
#ifdef _HB_OT_CORE_TABLE_UNDEF
#undef HB_OT_CORE_TABLE
#endif

View File

@ -63,10 +63,13 @@ struct hb_ot_face_t
hb_face_t *face; /* MUST be JUST before the lazy loaders. */
#define HB_OT_TABLE(Namespace, Type) \
hb_table_lazy_loader_t<Namespace::Type, HB_OT_TABLE_ORDER (Namespace, Type)> Type;
#define HB_OT_CORE_TABLE(Namespace, Type) \
hb_table_lazy_loader_t<Namespace::Type, HB_OT_TABLE_ORDER (Namespace, Type), true> Type;
#define HB_OT_ACCELERATOR(Namespace, Type) \
hb_face_lazy_loader_t<Namespace::Type##_accelerator_t, HB_OT_TABLE_ORDER (Namespace, Type)> Type;
#include "hb-ot-face-table-list.hh"
#undef HB_OT_ACCELERATOR
#undef HB_OT_CORE_TABLE
#undef HB_OT_TABLE
};

View File

@ -936,7 +936,7 @@ struct glyf
return;
short_offset = 0 == head.indexToLocFormat;
loca_table = hb_sanitize_context_t ().reference_table<loca> (face);
loca_table = face->table.loca.get_blob (); // Needs no destruct!
glyf_table = hb_sanitize_context_t ().reference_table<glyf> (face);
#ifndef HB_NO_VAR
gvar = face->table.gvar;
@ -951,7 +951,6 @@ struct glyf
}
~accelerator_t ()
{
loca_table.destroy ();
glyf_table.destroy ();
}

View File

@ -28,6 +28,7 @@
#define HB_OT_HMTX_TABLE_HH
#include "hb-open-type.hh"
#include "hb-ot-maxp-table.hh"
#include "hb-ot-hhea-table.hh"
#include "hb-ot-var-hvar-table.hh"
#include "hb-ot-metrics.hh"
@ -98,12 +99,12 @@ struct hmtxvmtx
hb_requires (hb_is_iterator (Iterator))>
void serialize (hb_serialize_context_t *c,
Iterator it,
unsigned num_advances)
unsigned num_long_metrics)
{
unsigned idx = 0;
for (auto _ : it)
{
if (idx < num_advances)
if (idx < num_long_metrics)
{
LongMetric lm;
lm.advance = _.first;
@ -128,17 +129,17 @@ struct hmtxvmtx
if (unlikely (!table_prime)) return_trace (false);
accelerator_t _mtx (c->plan->source);
unsigned num_advances;
unsigned num_long_metrics;
{
/* Determine num_advances to encode. */
/* Determine num_long_metrics to encode. */
auto& plan = c->plan;
num_advances = plan->num_output_glyphs ();
num_long_metrics = plan->num_output_glyphs ();
hb_codepoint_t old_gid = 0;
unsigned int last_advance = plan->old_gid_for_new_gid (num_advances - 1, &old_gid) ? _mtx.get_advance (old_gid) : 0;
while (num_advances > 1 &&
last_advance == (plan->old_gid_for_new_gid (num_advances - 2, &old_gid) ? _mtx.get_advance (old_gid) : 0))
unsigned int last_advance = plan->old_gid_for_new_gid (num_long_metrics - 1, &old_gid) ? _mtx.get_advance (old_gid) : 0;
while (num_long_metrics > 1 &&
last_advance == (plan->old_gid_for_new_gid (num_long_metrics - 2, &old_gid) ? _mtx.get_advance (old_gid) : 0))
{
num_advances--;
num_long_metrics--;
}
}
@ -153,13 +154,13 @@ struct hmtxvmtx
})
;
table_prime->serialize (c->serializer, it, num_advances);
table_prime->serialize (c->serializer, it, num_long_metrics);
if (unlikely (c->serializer->in_error ()))
return_trace (false);
// Amend header num hmetrics
if (unlikely (!subset_update_header (c->plan, num_advances)))
if (unlikely (!subset_update_header (c->plan, num_long_metrics)))
return_trace (false);
return_trace (true);
@ -172,9 +173,18 @@ struct hmtxvmtx
accelerator_t (hb_face_t *face,
unsigned int default_advance_ = 0)
{
table = hb_sanitize_context_t ().reference_table<hmtxvmtx> (face, T::tableTag);
var_table = hb_sanitize_context_t ().reference_table<HVARVVAR> (face, T::variationsTag);
default_advance = default_advance_ ? default_advance_ : hb_face_get_upem (face);
num_advances = T::is_horizontal ?
/* Populate count variables and sort them out as we go */
unsigned int len = table.get_length ();
if (len & 1)
len--;
num_long_metrics = T::is_horizontal ?
face->table.hhea->numberOfLongMetrics :
#ifndef HB_NO_VERTICAL
face->table.vhea->numberOfLongMetrics
@ -182,25 +192,27 @@ struct hmtxvmtx
0
#endif
;
if (unlikely (num_long_metrics * 4 > len))
num_long_metrics = len / 4;
len -= num_long_metrics * 4;
table = hb_sanitize_context_t ().reference_table<hmtxvmtx> (face, T::tableTag);
num_bearings = face->table.maxp->get_num_glyphs ();
/* Cap num_metrics() and num_advances() based on table length. */
unsigned int len = table.get_length ();
if (unlikely (num_advances * 4 > len))
num_advances = len / 4;
num_metrics = num_advances + (len - 4 * num_advances) / 2;
if (unlikely (num_bearings < num_long_metrics))
num_bearings = num_long_metrics;
if (unlikely ((num_bearings - num_long_metrics) * 2 > len))
num_bearings = num_long_metrics + len / 2;
len -= (num_bearings - num_long_metrics) * 2;
/* We MUST set num_metrics to zero if num_advances is zero.
/* We MUST set num_bearings to zero if num_long_metrics is zero.
* Our get_advance() depends on that. */
if (unlikely (!num_advances))
{
num_metrics = num_advances = 0;
table.destroy ();
table = hb_blob_get_empty ();
}
if (unlikely (!num_long_metrics))
num_bearings = num_long_metrics = 0;
var_table = hb_sanitize_context_t ().reference_table<HVARVVAR> (face, T::variationsTag);
num_advances = num_bearings + len / 2;
num_glyphs = face->get_num_glyphs ();
if (num_glyphs < num_advances)
num_glyphs = num_advances;
}
~accelerator_t ()
{
@ -210,14 +222,14 @@ struct hmtxvmtx
int get_side_bearing (hb_codepoint_t glyph) const
{
if (glyph < num_advances)
if (glyph < num_long_metrics)
return table->longMetricZ[glyph].sb;
if (unlikely (glyph >= num_metrics))
if (unlikely (glyph >= num_bearings))
return 0;
const FWORD *bearings = (const FWORD *) &table->longMetricZ[num_advances];
return bearings[glyph - num_advances];
const FWORD *bearings = (const FWORD *) &table->longMetricZ[num_long_metrics];
return bearings[glyph - num_long_metrics];
}
int get_side_bearing (hb_font_t *font, hb_codepoint_t glyph) const
@ -225,7 +237,7 @@ struct hmtxvmtx
int side_bearing = get_side_bearing (glyph);
#ifndef HB_NO_VAR
if (unlikely (glyph >= num_metrics) || !font->num_coords)
if (unlikely (glyph >= num_bearings) || !font->num_coords)
return side_bearing;
if (var_table.get_length ())
@ -239,18 +251,35 @@ struct hmtxvmtx
unsigned int get_advance (hb_codepoint_t glyph) const
{
if (unlikely (glyph >= num_metrics))
{
/* If num_metrics is zero, it means we don't have the metrics table
* for this direction: return default advance. Otherwise, it means that the
* glyph index is out of bound: return zero. */
if (num_metrics)
return 0;
else
return default_advance;
}
/* OpenType case. */
if (glyph < num_bearings)
return table->longMetricZ[hb_min (glyph, (uint32_t) num_long_metrics - 1)].advance;
return table->longMetricZ[hb_min (glyph, (uint32_t) num_advances - 1)].advance;
/* If num_advances is zero, it means we don't have the metrics table
* for this direction: return default advance. Otherwise, there's a
* well-defined answer. */
if (unlikely (!num_advances))
return default_advance;
#ifdef HB_NO_BORING_EXPANSION
return 0;
#endif
if (unlikely (glyph >= num_glyphs))
return 0;
/* num_bearings <= glyph < num_glyphs;
* num_bearings <= num_advances */
/* TODO Optimize */
if (num_bearings == num_advances)
return get_advance (num_bearings - 1);
const FWORD *bearings = (const FWORD *) &table->longMetricZ[num_long_metrics];
const UFWORD *advances = (const UFWORD *) &bearings[num_bearings - num_long_metrics];
return advances[hb_min (glyph - num_bearings, num_advances - num_bearings - 1)];
}
unsigned int get_advance (hb_codepoint_t glyph,
@ -259,7 +288,7 @@ struct hmtxvmtx
unsigned int advance = get_advance (glyph);
#ifndef HB_NO_VAR
if (unlikely (glyph >= num_metrics) || !font->num_coords)
if (unlikely (glyph >= num_bearings) || !font->num_coords)
return advance;
if (var_table.get_length ())
@ -272,8 +301,12 @@ struct hmtxvmtx
}
protected:
unsigned int num_metrics;
unsigned int num_advances;
// 0 <= num_long_metrics <= num_bearings <= num_advances <= num_glyphs
unsigned num_long_metrics;
unsigned num_bearings;
unsigned num_advances;
unsigned num_glyphs;
unsigned int default_advance;
private:
@ -305,6 +338,8 @@ struct hmtxvmtx
* the end. This allows a monospaced
* font to vary the side bearing
* values for each glyph. */
/*UnsizedArrayOf<UFWORD>advancesX;*/
/* TODO Document. */
public:
DEFINE_SIZE_ARRAY (0, longMetricZ);
};

View File

@ -33,6 +33,7 @@
#include "hb-aat-layout-feat-table.hh"
#include "hb-ot-layout-common.hh"
#include "hb-ot-cmap-table.hh"
#include "hb-ot-glyf-table.hh"
#include "hb-ot-head-table.hh"
#include "hb-ot-maxp-table.hh"
@ -55,17 +56,41 @@ const unsigned char _hb_Null_AAT_Lookup[2] = {0xFF, 0xFF};
/* hb_face_t */
static inline unsigned
load_num_glyphs_from_loca (const hb_face_t *face)
{
unsigned ret = 0;
unsigned indexToLocFormat = face->table.head->indexToLocFormat;
if (indexToLocFormat <= 1)
{
bool short_offset = 0 == indexToLocFormat;
hb_blob_t *loca_blob = face->table.loca.get_blob ();
ret = hb_max (1u, loca_blob->length / (short_offset ? 2 : 4)) - 1;
}
return ret;
}
static inline unsigned
load_num_glyphs_from_maxp (const hb_face_t *face)
{
return face->table.maxp->get_num_glyphs ();
}
unsigned int
hb_face_t::load_num_glyphs () const
{
hb_sanitize_context_t c = hb_sanitize_context_t ();
c.set_num_glyphs (0); /* So we don't recurse ad infinitum. */
hb_blob_t *maxp_blob = c.reference_table<OT::maxp> (this);
const OT::maxp *maxp_table = maxp_blob->as<OT::maxp> ();
unsigned ret = 0;
#ifndef HB_NO_BORING_EXPANSION
ret = hb_max (ret, load_num_glyphs_from_loca (this));
#endif
ret = hb_max (ret, load_num_glyphs_from_maxp (this));
unsigned int ret = maxp_table->get_num_glyphs ();
num_glyphs.set_relaxed (ret);
hb_blob_destroy (maxp_blob);
return ret;
}

View File

@ -30,6 +30,8 @@ noinst_PROGRAMS = $(TEST_PROGS)
TEST_PROGS = \
test-aat-layout \
test-baseline \
test-be-glyph-advance \
test-be-num-glyphs \
test-blob \
test-buffer \
test-c \

View File

@ -52,6 +52,17 @@ HB_BEGIN_DECLS
((const char *) s)[2], \
((const char *) s)[3]))
#define HB_FACE_ADD_TABLE(face, tag, data) \
do { \
hb_blob_t *blob = hb_blob_create_or_fail ((data), \
sizeof (data), \
HB_MEMORY_MODE_READONLY, \
NULL, NULL); \
hb_face_builder_add_table ((face), \
HB_TAG_CHAR4(tag), \
blob); \
hb_blob_destroy (blob); \
} while (0)
static inline const char *
srcdir (void)

View File

@ -6,6 +6,8 @@ endif
tests = [
'test-aat-layout.c',
'test-baseline.c',
'test-be-glyph-advance.c',
'test-be-num-glyphs.c',
'test-blob.c',
'test-buffer.c',
'test-c.c',

View File

@ -0,0 +1,101 @@
/*
* Copyright © 2022 Behdad Esfahbod
*
* 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.
*/
#include "hb-test.h"
#include <hb.h>
static void
test_maxp_and_hmtx (void)
{
hb_face_t *face;
hb_font_t *font;
const char maxp_data[] = "\x00\x00\x50\x00" // version
"\x00\x05" // numGlyphs
;
const char loca_data[18] = "";
const char hhea_data[36] =
"\x00\x01\x00\x00" /* FixedVersion<>version; * 0x00010000u for version 1.0. */
"\x02\x00" /* FWORD ascender; * Typographic ascent. */
"\x00\x10" /* FWORD descender; * Typographic descent. */
"\x00\x00" /* FWORD lineGap; * Typographic line gap. */
"\x00\x00" /* UFWORD advanceMax; * Maximum advance width/height value in metrics table. */
"\x00\x00" /* FWORD minLeadingBearing; * Minimum left/top sidebearing value in metrics table. */
"\x00\x00" /* FWORD minTrailingBearing; * Minimum right/bottom sidebearing value; */
"\x01\x00" /* FWORD maxExtent; * horizontal: Max(lsb + (xMax - xMin)), */
"\x00\x00" /* HBINT16 caretSlopeRise; * Used to calculate the slope of the,*/
"\x00\x00" /* HBINT16 caretSlopeRun; * 0 for vertical caret, 1 for horizontal. */
"\x00\x00" /* HBINT16 caretOffset; * The amount by which a slanted */
"\x00\x00" /* HBINT16 reserved1; * Set to 0. */
"\x00\x00" /* HBINT16 reserved2; * Set to 0. */
"\x00\x00" /* HBINT16 reserved3; * Set to 0. */
"\x00\x00" /* HBINT16 reserved4; * Set to 0. */
"\x00\x00" /* HBINT16 metricDataFormat; * 0 for current format. */
"\x00\x02" /* HBUINT16 numberOfLongMetrics; * Number of LongMetric entries in metric table. */
;
const char hmtx_data[18] =
"\x00\x01\x00\x02" /* glyph 0 advance lsb */
"\x00\x03\x00\x04" /* glyph 1 advance lsb */
"\x00\x05" /* glyph 2 lsb */
"\x00\x06" /* glyph 3 lsb */
"\x00\x07" /* glyph 4 lsb */
"\x00\x08" /* glyph 5 advance */
"\x00\x09" /* glyph 6 advance */
;
face = hb_face_builder_create ();
HB_FACE_ADD_TABLE (face, "maxp", maxp_data);
HB_FACE_ADD_TABLE (face, "loca", loca_data);
HB_FACE_ADD_TABLE (face, "hhea", hhea_data);
HB_FACE_ADD_TABLE (face, "hmtx", hmtx_data);
font = hb_font_create (face);
hb_face_destroy (face);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 0), ==, 1);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 1), ==, 3);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 2), ==, 3);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 3), ==, 3);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 4), ==, 3);
#ifndef HB_NO_BORING_EXPANSION
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 5), ==, 8);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 6), ==, 9);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 7), ==, 9);
#endif
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 8), ==, 0);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font, 9), ==, 0);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font,10), ==, 0);
g_assert_cmpuint (hb_font_get_glyph_h_advance (font,11), ==, 0);
hb_font_destroy (font);
}
int
main (int argc, char **argv)
{
hb_test_init (&argc, &argv);
hb_test_add (test_maxp_and_hmtx);
return hb_test_run();
}

View File

@ -0,0 +1,71 @@
/*
* Copyright © 2022 Behdad Esfahbod
*
* 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.
*/
#include "hb-test.h"
#include <hb.h>
static void
test_maxp_and_loca (void)
{
hb_face_t *face;
const char maxp_data[] = "\x00\x00\x50\x00" // version
"\x00\x05" // numGlyphs
;
const char loca_data[18] = "";
face = hb_face_builder_create ();
g_assert_cmpuint (hb_face_get_glyph_count (face), ==, 0);
hb_face_destroy (face);
face = hb_face_builder_create ();
HB_FACE_ADD_TABLE (face, "maxp", maxp_data);
g_assert_cmpuint (hb_face_get_glyph_count (face), ==, 5);
hb_face_destroy (face);
#ifndef HB_NO_BORING_EXPANSION
face = hb_face_builder_create ();
HB_FACE_ADD_TABLE (face, "maxp", maxp_data);
HB_FACE_ADD_TABLE (face, "loca", loca_data);
g_assert_cmpuint (hb_face_get_glyph_count (face), ==, 8);
hb_face_destroy (face);
face = hb_face_builder_create ();
HB_FACE_ADD_TABLE (face, "loca", loca_data);
g_assert_cmpuint (hb_face_get_glyph_count (face), ==, 8);
hb_face_destroy (face);
#endif
}
int
main (int argc, char **argv)
{
hb_test_init (&argc, &argv);
hb_test_add (test_maxp_and_loca);
return hb_test_run();
}