From 5fbcb992bd7887f95b1cf2ca045c9226de8148fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Wang?= Date: Thu, 25 Aug 2016 10:47:15 +0200 Subject: [PATCH] MATH Table: Add API to check availability of math data. --- src/Makefile.sources | 1 + src/hb-ot-layout-math-table.hh | 60 ++++++++++++++++ src/hb-ot-layout-private.hh | 3 + src/hb-ot-layout.cc | 45 ++++++++++++ src/hb-ot-layout.h | 7 ++ test/api/Makefile.am | 14 +++- test/api/fonts/MathTestFontEmpty.otf | Bin 0 -> 14320 bytes test/api/fonts/MathTestFontNone.otf | Bin 0 -> 14284 bytes test/api/test-ot-layout-math.c | 98 +++++++++++++++++++++++++++ 9 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/hb-ot-layout-math-table.hh create mode 100644 test/api/fonts/MathTestFontEmpty.otf create mode 100644 test/api/fonts/MathTestFontNone.otf create mode 100644 test/api/test-ot-layout-math.c diff --git a/src/Makefile.sources b/src/Makefile.sources index ac806838c..cd30b12f0 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -75,6 +75,7 @@ HB_OT_sources = \ hb-ot-layout-gsubgpos-private.hh \ hb-ot-layout-gsub-table.hh \ hb-ot-layout-jstf-table.hh \ + hb-ot-layout-math-table.hh \ hb-ot-layout-private.hh \ hb-ot-map.cc \ hb-ot-map-private.hh \ diff --git a/src/hb-ot-layout-math-table.hh b/src/hb-ot-layout-math-table.hh new file mode 100644 index 000000000..f4ecf3f0e --- /dev/null +++ b/src/hb-ot-layout-math-table.hh @@ -0,0 +1,60 @@ +/* + * Copyright © 2016 Igalia S.L. + * + * 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. + * + * Igalia Author(s): Frédéric Wang + */ + +#ifndef HB_OT_LAYOUT_MATH_TABLE_HH +#define HB_OT_LAYOUT_MATH_TABLE_HH + +#include "hb-open-type-private.hh" +#include "hb-ot-layout-common-private.hh" + +namespace OT { + +/* + * MATH -- The MATH Table + */ + +struct MATH +{ + static const hb_tag_t tableTag = HB_OT_TAG_MATH; + + inline bool sanitize (hb_sanitize_context_t *c) const + { + TRACE_SANITIZE (this); + return_trace (version.sanitize (c) && + likely (version.major == 1)); + } + +protected: + FixedVersion<>version; /* Version of the MATH table + initially set to 0x00010000u */ +public: + DEFINE_SIZE_STATIC (4); +}; + +} /* mathspace OT */ + + +#endif /* HB_OT_LAYOUT_MATH_TABLE_HH */ diff --git a/src/hb-ot-layout-private.hh b/src/hb-ot-layout-private.hh index 778b2c442..a4272de63 100644 --- a/src/hb-ot-layout-private.hh +++ b/src/hb-ot-layout-private.hh @@ -124,6 +124,7 @@ namespace OT { struct GDEF; struct GSUB; struct GPOS; + struct MATH; } struct hb_ot_layout_lookup_accelerator_t @@ -152,10 +153,12 @@ struct hb_ot_layout_t hb_blob_t *gdef_blob; hb_blob_t *gsub_blob; hb_blob_t *gpos_blob; + hb_blob_t *math_blob; const struct OT::GDEF *gdef; const struct OT::GSUB *gsub; const struct OT::GPOS *gpos; + const struct OT::MATH *math; unsigned int gsub_lookup_count; unsigned int gpos_lookup_count; diff --git a/src/hb-ot-layout.cc b/src/hb-ot-layout.cc index 5cb1491c3..24d290c5c 100644 --- a/src/hb-ot-layout.cc +++ b/src/hb-ot-layout.cc @@ -35,6 +35,7 @@ #include "hb-ot-layout-gsub-table.hh" #include "hb-ot-layout-gpos-table.hh" #include "hb-ot-layout-jstf-table.hh" +#include "hb-ot-layout-math-table.hh" #include "hb-ot-map-private.hh" @@ -60,6 +61,10 @@ _hb_ot_layout_create (hb_face_t *face) layout->gpos_blob = OT::Sanitizer::sanitize (face->reference_table (HB_OT_TAG_GPOS)); layout->gpos = OT::Sanitizer::lock_instance (layout->gpos_blob); + // The MATH table is rarer so we only try and load it in _get_math + layout->math_blob = NULL; + layout->math = NULL; + { /* * The ugly business of blacklisting individual fonts' tables happen here! @@ -178,6 +183,8 @@ _hb_ot_layout_destroy (hb_ot_layout_t *layout) hb_blob_destroy (layout->gsub_blob); hb_blob_destroy (layout->gpos_blob); + if (layout->math_blob) hb_blob_destroy (layout->math_blob); + free (layout); } @@ -199,6 +206,21 @@ _get_gpos (hb_face_t *face) if (unlikely (!hb_ot_shaper_face_data_ensure (face))) return OT::Null(OT::GPOS); return *hb_ot_layout_from_face (face)->gpos; } +static inline const OT::MATH& +_get_math (hb_face_t *face) +{ + if (unlikely (!hb_ot_shaper_face_data_ensure (face))) return OT::Null(OT::MATH); + + hb_ot_layout_t * layout = hb_ot_layout_from_face (face); + + // If the MATH table is not loaded yet, do it now. + if (!layout->math_blob) { + layout->math_blob = OT::Sanitizer::sanitize (face->reference_table (HB_OT_TAG_MATH)); + layout->math = OT::Sanitizer::lock_instance (layout->math_blob); + } + + return *layout->math; +} /* @@ -1190,3 +1212,26 @@ hb_ot_layout_substitute_lookup (OT::hb_apply_context_t *c, { apply_string (c, lookup, accel); } + +/* + * OT::MATH + */ + +/** + * hb_ot_layout_has_math_data: + * + * @face: #hb_face_t to test + * + * This function allows to verify the presence of an OpenType MATH table on the + * face. If so, such a table will be loaded into memory and sanitized. You can + * then safely call other functions for math layout and shaping. + * + * Return value: #TRUE if face has a MATH table and #FALSE otherwise + * + * Since: ???? + **/ +hb_bool_t +hb_ot_layout_has_math_data (hb_face_t *face) +{ + return &_get_math (face) != &OT::Null(OT::MATH); +} diff --git a/src/hb-ot-layout.h b/src/hb-ot-layout.h index eb23d45b6..7cbd794ce 100644 --- a/src/hb-ot-layout.h +++ b/src/hb-ot-layout.h @@ -42,6 +42,7 @@ HB_BEGIN_DECLS #define HB_OT_TAG_GSUB HB_TAG('G','S','U','B') #define HB_OT_TAG_GPOS HB_TAG('G','P','O','S') #define HB_OT_TAG_JSTF HB_TAG('J','S','T','F') +#define HB_OT_TAG_MATH HB_TAG('M','A','T','H') /* @@ -297,6 +298,12 @@ hb_ot_layout_get_size_params (hb_face_t *face, unsigned int *range_end /* OUT. May be NULL */); +/* + * MATH + */ + +HB_EXTERN hb_bool_t +hb_ot_layout_has_math_data (hb_face_t *face); HB_END_DECLS #endif /* HB_OT_LAYOUT_H */ diff --git a/test/api/Makefile.am b/test/api/Makefile.am index d7d40af39..dae8700c3 100644 --- a/test/api/Makefile.am +++ b/test/api/Makefile.am @@ -43,10 +43,22 @@ endif if HAVE_OT + TEST_PROGS += \ test-ot-tag \ $(NULL) -endif + +if HAVE_FREETYPE +TEST_PROGS += \ + test-ot-layout-math \ + $(NULL) +test_ot_layout_math_LDADD = $(LDADD) +test_ot_layout_math_CPPFLAGS = $(AM_CPPFLAGS) +test_ot_layout_math_CPPFLAGS += $(FREETYPE_CFLAGS) +test_ot_layout_math_LDADD += $(FREETYPE_LIBS) +endif # HAVE_FREETYPE + +endif # HAVE_OT # Tests for header compilation TEST_PROGS += \ diff --git a/test/api/fonts/MathTestFontEmpty.otf b/test/api/fonts/MathTestFontEmpty.otf new file mode 100644 index 0000000000000000000000000000000000000000..6b50d66fc3d653da18010d8c116e3a7778dda109 GIT binary patch literal 14320 zcmds8&5tALS$}(acCu79vzb*uE3&|=21V0~+w-|I(j*5w{+PDbZF_C^Oy+<>wOwvk z#;$Uz%H7iv2P6a%3I{mw4UCAQ?e1kc zL}GeuSG^z4_w#$6x7^(s3_8kDf2_8Zt~MI_AJl*Tmr@V!E2aMSpfcE3TewmW{}pde zr+@42d;juvN2#r^@9z}3U;f5-LD z@cO;UEP1*83tXv(e7`nxU(VH)E-ts;=6&GKyuYs9+{67`sr%obhjH@e&(uAv{|4r@ zkp=R6@=xtQ{r<2^F`rm) zV;gTmWlP<_om^j0U%By(^!jRU{95k%y86u<--Oq;u0o!jYd1bjuea3Bz3-&gSJd@; ze~?~Z&5ggMe*4~wbo^Rw{JOe-@6W5@{4Da1CyBl}+|_q)-+55d&0}}!ySiU)mG#X@ zlFT37y5*a(@-Up;+I5`4#E*3&3=-WB$H}Q1c^U&#f9M6VH`0q>CW5> zWN}Ltl=M?Civ2LqcglClLM+`&GQ)80gUFFL4Nptj4MrSvr*Wv=6W5=*M^jIq`pHDQ zx>5O9yU8Ob6*3-1{yd4xu|F+`(ebTLqvbfx-k1O982P*Pwr+IV1Kny?>+OC$FTUQ> zcOU46cN8t$=nMwD{qX<9(CPN-m4nBvI?Uqf;}Cl5aBOUO&W`#f6x`LceiG_9@n?&v zo4`0>G##D#BhMLmC$Qpt=D|~-G7P6MUl_TGf8yye{Wp)oA9%wgE*TPw`8IcUQAFPKUKASB9Kk+>fqt_r4sGmR%cxoJZ_y|T$ z$Y>ZxNPdX
  • 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 literal 0 HcmV?d00001 diff --git a/test/api/fonts/MathTestFontNone.otf b/test/api/fonts/MathTestFontNone.otf new file mode 100644 index 0000000000000000000000000000000000000000..52984eecc86b079a036841bfdce1b038eaf3ce0e GIT binary patch literal 14284 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