From 319ff59161e6b027391f8b9fdce0db6dd44cc20d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Fr=C3=A9d=C3=A9ric=20Wang?= Date: Thu, 25 Aug 2016 11:06:41 +0200 Subject: [PATCH] MATH Table: Add API to access math contants. --- src/Makefile.sources | 1 + src/hb-ot-layout-math-table.hh | 146 +++++++++++++++++++++++++++++++++++- src/hb-ot-layout.cc | 25 ++++++ src/hb-ot-layout.h | 5 ++ src/hb-ot-math.h | 100 ++++++++++++++++++++++++ test/api/fonts/MathTestFontFull.otf | Bin 0 -> 25412 bytes test/api/test-ot-layout-math.c | 72 ++++++++++++++++++ 7 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 src/hb-ot-math.h create mode 100644 test/api/fonts/MathTestFontFull.otf diff --git a/src/Makefile.sources b/src/Makefile.sources index cd30b12..5c695c5 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -112,6 +112,7 @@ HB_OT_headers = \ hb-ot.h \ hb-ot-font.h \ hb-ot-layout.h \ + hb-ot-math.h \ hb-ot-shape.h \ hb-ot-tag.h \ $(NULL) diff --git a/src/hb-ot-layout-math-table.hh b/src/hb-ot-layout-math-table.hh index 7b3c0c7..02a1d45 100644 --- a/src/hb-ot-layout-math-table.hh +++ b/src/hb-ot-layout-math-table.hh @@ -29,9 +29,139 @@ #include "hb-open-type-private.hh" #include "hb-ot-layout-common-private.hh" +#include "hb-ot-math.h" namespace OT { + +struct MathValueRecord +{ + inline hb_position_t get_value (hb_font_t *font, bool horizontal, + const void *base) const + { + return horizontal ? + font->em_scale_x (value) + (base+deviceTable).get_x_delta (font) : + font->em_scale_y (value) + (base+deviceTable).get_y_delta (font); + } + + inline bool sanitize (hb_sanitize_context_t *c, const void *base) const + { + TRACE_SANITIZE (this); + return_trace (c->check_struct (this) && deviceTable.sanitize (c, base)); + } + +protected: + SHORT value; /* The X or Y value in design units */ + OffsetTo deviceTable; /* Offset to the device table - from the + beginning of parent table. May be NULL. + Suggested format for device table is 1. */ + +public: + DEFINE_SIZE_STATIC (2 * 2); +}; + +struct MathConstants +{ + inline bool sanitize_math_value_records (hb_sanitize_context_t *c) const + { + TRACE_SANITIZE (this); + unsigned int count = + HB_OT_MATH_CONSTANT_RADICAL_KERN_AFTER_DEGREE - + HB_OT_MATH_CONSTANT_MATH_LEADING + 1; + for (unsigned int i = 0; i < count; i++) + if (!mathValueRecords[i].sanitize (c, this)) return_trace (false); + return_trace (true); + } + + inline bool sanitize (hb_sanitize_context_t *c) const + { + TRACE_SANITIZE (this); + return_trace (c->check_struct (this) && sanitize_math_value_records(c)); + } + + inline hb_position_t get_value (hb_font_t *font, hb_ot_math_constant_t constant) const + { + switch (constant) { + case HB_OT_MATH_CONSTANT_DELIMITED_SUB_FORMULA_MIN_HEIGHT: + case HB_OT_MATH_CONSTANT_DISPLAY_OPERATOR_MIN_HEIGHT: + return font->em_scale_y (minHeight[constant - HB_OT_MATH_CONSTANT_DELIMITED_SUB_FORMULA_MIN_HEIGHT]); + + case HB_OT_MATH_CONSTANT_RADICAL_KERN_AFTER_DEGREE: + case HB_OT_MATH_CONSTANT_RADICAL_KERN_BEFORE_DEGREE: + case HB_OT_MATH_CONSTANT_SKEWED_FRACTION_HORIZONTAL_GAP: + case HB_OT_MATH_CONSTANT_SPACE_AFTER_SCRIPT: + return mathValueRecords[constant - HB_OT_MATH_CONSTANT_MATH_LEADING].get_value(font, true, this); + + case HB_OT_MATH_CONSTANT_ACCENT_BASE_HEIGHT: + case HB_OT_MATH_CONSTANT_AXIS_HEIGHT: + case HB_OT_MATH_CONSTANT_FLATTENED_ACCENT_BASE_HEIGHT: + case HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_DISPLAY_STYLE_SHIFT_DOWN: + case HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_GAP_MIN: + case HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_SHIFT_DOWN: + case HB_OT_MATH_CONSTANT_FRACTION_DENOM_DISPLAY_STYLE_GAP_MIN: + case HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_DISPLAY_STYLE_SHIFT_UP: + case HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_GAP_MIN: + case HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_SHIFT_UP: + case HB_OT_MATH_CONSTANT_FRACTION_NUM_DISPLAY_STYLE_GAP_MIN: + case HB_OT_MATH_CONSTANT_FRACTION_RULE_THICKNESS: + case HB_OT_MATH_CONSTANT_LOWER_LIMIT_BASELINE_DROP_MIN: + case HB_OT_MATH_CONSTANT_LOWER_LIMIT_GAP_MIN: + case HB_OT_MATH_CONSTANT_MATH_LEADING: + case HB_OT_MATH_CONSTANT_OVERBAR_EXTRA_ASCENDER: + case HB_OT_MATH_CONSTANT_OVERBAR_RULE_THICKNESS: + case HB_OT_MATH_CONSTANT_OVERBAR_VERTICAL_GAP: + case HB_OT_MATH_CONSTANT_RADICAL_DISPLAY_STYLE_VERTICAL_GAP: + case HB_OT_MATH_CONSTANT_RADICAL_EXTRA_ASCENDER: + case HB_OT_MATH_CONSTANT_RADICAL_RULE_THICKNESS: + case HB_OT_MATH_CONSTANT_RADICAL_VERTICAL_GAP: + case HB_OT_MATH_CONSTANT_SKEWED_FRACTION_VERTICAL_GAP: + case HB_OT_MATH_CONSTANT_STACK_BOTTOM_DISPLAY_STYLE_SHIFT_DOWN: + case HB_OT_MATH_CONSTANT_STACK_BOTTOM_SHIFT_DOWN: + case HB_OT_MATH_CONSTANT_STACK_DISPLAY_STYLE_GAP_MIN: + case HB_OT_MATH_CONSTANT_STACK_GAP_MIN: + case HB_OT_MATH_CONSTANT_STACK_TOP_DISPLAY_STYLE_SHIFT_UP: + case HB_OT_MATH_CONSTANT_STACK_TOP_SHIFT_UP: + case HB_OT_MATH_CONSTANT_STRETCH_STACK_BOTTOM_SHIFT_DOWN: + case HB_OT_MATH_CONSTANT_STRETCH_STACK_GAP_ABOVE_MIN: + case HB_OT_MATH_CONSTANT_STRETCH_STACK_GAP_BELOW_MIN: + case HB_OT_MATH_CONSTANT_STRETCH_STACK_TOP_SHIFT_UP: + case HB_OT_MATH_CONSTANT_SUBSCRIPT_BASELINE_DROP_MIN: + case HB_OT_MATH_CONSTANT_SUBSCRIPT_SHIFT_DOWN: + case HB_OT_MATH_CONSTANT_SUBSCRIPT_TOP_MAX: + case HB_OT_MATH_CONSTANT_SUB_SUPERSCRIPT_GAP_MIN: + case HB_OT_MATH_CONSTANT_SUPERSCRIPT_BASELINE_DROP_MAX: + case HB_OT_MATH_CONSTANT_SUPERSCRIPT_BOTTOM_MAX_WITH_SUBSCRIPT: + case HB_OT_MATH_CONSTANT_SUPERSCRIPT_BOTTOM_MIN: + case HB_OT_MATH_CONSTANT_SUPERSCRIPT_SHIFT_UP: + case HB_OT_MATH_CONSTANT_SUPERSCRIPT_SHIFT_UP_CRAMPED: + case HB_OT_MATH_CONSTANT_UNDERBAR_EXTRA_DESCENDER: + case HB_OT_MATH_CONSTANT_UNDERBAR_RULE_THICKNESS: + case HB_OT_MATH_CONSTANT_UNDERBAR_VERTICAL_GAP: + case HB_OT_MATH_CONSTANT_UPPER_LIMIT_BASELINE_RISE_MIN: + case HB_OT_MATH_CONSTANT_UPPER_LIMIT_GAP_MIN: + return mathValueRecords[constant - HB_OT_MATH_CONSTANT_MATH_LEADING].get_value(font, false, this); + + case HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN: + case HB_OT_MATH_CONSTANT_SCRIPT_SCRIPT_PERCENT_SCALE_DOWN: + return percentScaleDown[constant - HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN]; + + case HB_OT_MATH_CONSTANT_RADICAL_DEGREE_BOTTOM_RAISE_PERCENT: + return radicalDegreeBottomRaisePercent; + } + } + +protected: + SHORT percentScaleDown[HB_OT_MATH_CONSTANT_SCRIPT_SCRIPT_PERCENT_SCALE_DOWN - HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN + 1]; + USHORT minHeight[HB_OT_MATH_CONSTANT_DISPLAY_OPERATOR_MIN_HEIGHT - HB_OT_MATH_CONSTANT_DELIMITED_SUB_FORMULA_MIN_HEIGHT + 1]; + MathValueRecord mathValueRecords[HB_OT_MATH_CONSTANT_RADICAL_KERN_AFTER_DEGREE - HB_OT_MATH_CONSTANT_MATH_LEADING + 1]; + SHORT radicalDegreeBottomRaisePercent; + +public: + DEFINE_SIZE_STATIC (2 * (HB_OT_MATH_CONSTANT_DISPLAY_OPERATOR_MIN_HEIGHT - HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN + 1) + + 4 * (HB_OT_MATH_CONSTANT_RADICAL_KERN_AFTER_DEGREE - HB_OT_MATH_CONSTANT_MATH_LEADING + 1) + + 2); +}; + /* * MATH -- The MATH Table */ @@ -44,14 +174,22 @@ struct MATH { TRACE_SANITIZE (this); return_trace (version.sanitize (c) && - likely (version.major == 1)); + likely (version.major == 1) && + mathConstants.sanitize (c, this)); + } + + inline bool has_math_constants (void) const { return mathConstants != 0; } + inline const MathConstants &get_math_constants (void) const { + return this+mathConstants; } protected: - FixedVersion<>version; /* Version of the MATH table - * initially set to 0x00010000u */ + FixedVersion<>version; /* Version of the MATH table + * initially set to 0x00010000u */ + OffsetTo mathConstants; /* MathConstants table */ + public: - DEFINE_SIZE_STATIC (4); + DEFINE_SIZE_STATIC (6); }; } /* mathspace OT */ diff --git a/src/hb-ot-layout.cc b/src/hb-ot-layout.cc index e6316e1..2ae1388 100644 --- a/src/hb-ot-layout.cc +++ b/src/hb-ot-layout.cc @@ -1244,3 +1244,28 @@ hb_ot_layout_has_math_data (hb_face_t *face) { return &_get_math (face) != &OT::Null(OT::MATH); } + +/** + * hb_ot_layout_get_math_constant: + * + * @font: #hb_font_t from which to retrieve the value + * @constant: #hb_ot_math_constant_t the constant to retrieve + * + * This function returns the requested math constants as a #hb_position_t. + * If the request constant is HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN, + * HB_OT_MATH_CONSTANT_SCRIPT_SCRIPT_PERCENT_SCALE_DOWN or + * HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN then the return value is + * actually an integer between 0 and 100 representing that percentage. + * + * Return value: the requested constant or 0 + * + * Since: ???? + **/ +hb_position_t +hb_ot_layout_get_math_constant (hb_font_t *font, + hb_ot_math_constant_t constant) +{ + const OT::MATH &math = _get_math (font->face); + return math.has_math_constants() ? + math.get_math_constants().get_value(font, constant) : 0; +} diff --git a/src/hb-ot-layout.h b/src/hb-ot-layout.h index e786790..501082e 100644 --- a/src/hb-ot-layout.h +++ b/src/hb-ot-layout.h @@ -33,6 +33,7 @@ #include "hb.h" +#include "hb-ot-math.h" #include "hb-ot-tag.h" HB_BEGIN_DECLS @@ -305,6 +306,10 @@ hb_ot_layout_get_size_params (hb_face_t *face, HB_EXTERN hb_bool_t hb_ot_layout_has_math_data (hb_face_t *face); +HB_EXTERN hb_position_t +hb_ot_layout_get_math_constant (hb_font_t *font, + hb_ot_math_constant_t constant); + HB_END_DECLS diff --git a/src/hb-ot-math.h b/src/hb-ot-math.h new file mode 100644 index 0000000..7f0ec27 --- /dev/null +++ b/src/hb-ot-math.h @@ -0,0 +1,100 @@ +/* + * 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_H_IN +#error "Include instead." +#endif + +#ifndef HB_OT_MATH_H +#define HB_OT_MATH_H + +#include "hb.h" + +HB_BEGIN_DECLS + + +typedef enum { + HB_OT_MATH_CONSTANT_SCRIPT_PERCENT_SCALE_DOWN = 0, + HB_OT_MATH_CONSTANT_SCRIPT_SCRIPT_PERCENT_SCALE_DOWN = 1, + HB_OT_MATH_CONSTANT_DELIMITED_SUB_FORMULA_MIN_HEIGHT = 2, + HB_OT_MATH_CONSTANT_DISPLAY_OPERATOR_MIN_HEIGHT = 3, + HB_OT_MATH_CONSTANT_MATH_LEADING = 4, + HB_OT_MATH_CONSTANT_AXIS_HEIGHT = 5, + HB_OT_MATH_CONSTANT_ACCENT_BASE_HEIGHT = 6, + HB_OT_MATH_CONSTANT_FLATTENED_ACCENT_BASE_HEIGHT = 7, + HB_OT_MATH_CONSTANT_SUBSCRIPT_SHIFT_DOWN = 8, + HB_OT_MATH_CONSTANT_SUBSCRIPT_TOP_MAX = 9, + HB_OT_MATH_CONSTANT_SUBSCRIPT_BASELINE_DROP_MIN = 10, + HB_OT_MATH_CONSTANT_SUPERSCRIPT_SHIFT_UP = 11, + HB_OT_MATH_CONSTANT_SUPERSCRIPT_SHIFT_UP_CRAMPED = 12, + HB_OT_MATH_CONSTANT_SUPERSCRIPT_BOTTOM_MIN = 13, + HB_OT_MATH_CONSTANT_SUPERSCRIPT_BASELINE_DROP_MAX = 14, + HB_OT_MATH_CONSTANT_SUB_SUPERSCRIPT_GAP_MIN = 15, + HB_OT_MATH_CONSTANT_SUPERSCRIPT_BOTTOM_MAX_WITH_SUBSCRIPT = 16, + HB_OT_MATH_CONSTANT_SPACE_AFTER_SCRIPT = 17, + HB_OT_MATH_CONSTANT_UPPER_LIMIT_GAP_MIN = 18, + HB_OT_MATH_CONSTANT_UPPER_LIMIT_BASELINE_RISE_MIN = 19, + HB_OT_MATH_CONSTANT_LOWER_LIMIT_GAP_MIN = 20, + HB_OT_MATH_CONSTANT_LOWER_LIMIT_BASELINE_DROP_MIN = 21, + HB_OT_MATH_CONSTANT_STACK_TOP_SHIFT_UP = 22, + HB_OT_MATH_CONSTANT_STACK_TOP_DISPLAY_STYLE_SHIFT_UP = 23, + HB_OT_MATH_CONSTANT_STACK_BOTTOM_SHIFT_DOWN = 24, + HB_OT_MATH_CONSTANT_STACK_BOTTOM_DISPLAY_STYLE_SHIFT_DOWN = 25, + HB_OT_MATH_CONSTANT_STACK_GAP_MIN = 26, + HB_OT_MATH_CONSTANT_STACK_DISPLAY_STYLE_GAP_MIN = 27, + HB_OT_MATH_CONSTANT_STRETCH_STACK_TOP_SHIFT_UP = 28, + HB_OT_MATH_CONSTANT_STRETCH_STACK_BOTTOM_SHIFT_DOWN = 29, + HB_OT_MATH_CONSTANT_STRETCH_STACK_GAP_ABOVE_MIN = 30, + HB_OT_MATH_CONSTANT_STRETCH_STACK_GAP_BELOW_MIN = 31, + HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_SHIFT_UP = 32, + HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_DISPLAY_STYLE_SHIFT_UP = 33, + HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_SHIFT_DOWN = 34, + HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_DISPLAY_STYLE_SHIFT_DOWN = 35, + HB_OT_MATH_CONSTANT_FRACTION_NUMERATOR_GAP_MIN = 36, + HB_OT_MATH_CONSTANT_FRACTION_NUM_DISPLAY_STYLE_GAP_MIN = 37, + HB_OT_MATH_CONSTANT_FRACTION_RULE_THICKNESS = 38, + HB_OT_MATH_CONSTANT_FRACTION_DENOMINATOR_GAP_MIN = 39, + HB_OT_MATH_CONSTANT_FRACTION_DENOM_DISPLAY_STYLE_GAP_MIN = 40, + HB_OT_MATH_CONSTANT_SKEWED_FRACTION_HORIZONTAL_GAP = 41, + HB_OT_MATH_CONSTANT_SKEWED_FRACTION_VERTICAL_GAP = 42, + HB_OT_MATH_CONSTANT_OVERBAR_VERTICAL_GAP = 43, + HB_OT_MATH_CONSTANT_OVERBAR_RULE_THICKNESS = 44, + HB_OT_MATH_CONSTANT_OVERBAR_EXTRA_ASCENDER = 45, + HB_OT_MATH_CONSTANT_UNDERBAR_VERTICAL_GAP = 46, + HB_OT_MATH_CONSTANT_UNDERBAR_RULE_THICKNESS = 47, + HB_OT_MATH_CONSTANT_UNDERBAR_EXTRA_DESCENDER = 48, + HB_OT_MATH_CONSTANT_RADICAL_VERTICAL_GAP = 49, + HB_OT_MATH_CONSTANT_RADICAL_DISPLAY_STYLE_VERTICAL_GAP = 50, + HB_OT_MATH_CONSTANT_RADICAL_RULE_THICKNESS = 51, + HB_OT_MATH_CONSTANT_RADICAL_EXTRA_ASCENDER = 52, + HB_OT_MATH_CONSTANT_RADICAL_KERN_BEFORE_DEGREE = 53, + HB_OT_MATH_CONSTANT_RADICAL_KERN_AFTER_DEGREE = 54, + HB_OT_MATH_CONSTANT_RADICAL_DEGREE_BOTTOM_RAISE_PERCENT = 55 +} hb_ot_math_constant_t; + +HB_END_DECLS + +#endif /* HB_OT_MATH_H */ diff --git a/test/api/fonts/MathTestFontFull.otf b/test/api/fonts/MathTestFontFull.otf new file mode 100644 index 0000000000000000000000000000000000000000..6c7c9a9511ce10cad109816e6683bcdc4d5adaa4 GIT binary patch literal 25412 zcmeHvdwf*oo%i$1Et8WFAOf{EwFjgMHiWAnQIsK>KqNPlphEF7NhZnYWG2o`2*I>v zTVJK3h*n)|TT892t*c#^($-#Zt?Q*PYrDSQdRdn(t!u4qy%!Lv?DzXT=b1S(nSizX z*}i`?@S8c$?RUSOGn2G-cD9N#u~~Sn=6w+};;9ASJH!1xW!KbEjDy0% zS^VIP+8O8TD~qP=Qke9be#K7ku?+*C_eHr~_;I4#y9(p4>37d^afVzhfYM*8kKmR3 z*x3;;HVgZtyEQFmo_p|vIUD~tnj{`o@3@@on_)TEQcPMn-?{dPl7;QgwO3RxH2nI+ zq(xq5-jD0W&UFCSG3UBMoUrIVSXcVM=Zqjpt7|Fjg%h1?kEmF-U|BM>&D!ahk-Jjr%tCaPwF;g zt5M+QET697hH?zzoaUqp?{UlwE6qd{UuGv|Ph-u7JR1Npj3c58ujF?aWAe^b@){E7 zs_P82XBB8N4f$!6L7Yf~Q}P|hdlIvK&}l2qHz~bZV$NUXpXJhjn+S^*B~vTjJ8{(n z+tjJq4!n-1eH)z9(-y0jsn-pTFAicq zBY^R%d7;n76^1Tcr9gvNx!(q_CjJJok`$L>wj1kd<68E}WzvYFg9)Gaxk+QJj%S+R=

WsnN!JE?xZBf> zrL||%4{2|$9ZE$XVQ$tsib5w`>%l)WcXVxNFPICdSeJ6rxih`yk8Amr&UlUXU+RR~ zAoto5)DN{s8>WDy95O z>(+3hHB9cQU3!W39(NTd70Q^418QJI#jD93`M#dxS6k(BSTLq^l*@kf7}_G|ImF6} zOl}y_IzHW;Q18Y14jX=u#3}09NEQKqgOF+jTF`in9NgOtj&i)@GNh57Rb2b6_WXQ4 zHRpPTa*;l5pYPQsThyZ(HK5A206GDzydB_b#!>pK8IB%$6<(cHs2jr#?Ubt_%nY<2 z_l>osJA&;ZounmJK_iTI$}kI9SnJ-M-i-$`+UY;6ee@ODFC{L+9OFf2pEPhXcN_1} zmejf1Qu;PILuc_M_NQ=yqnw1yFU$y(n7H%%FCCGbFSN4GHq3uIhtEL2kRP+|uCxU_ z(?7+jCUw&`TDnO1I3A$g!7iD7 zGWXlCDPurKDebLi*~2i}NL}wXyyo|&#Wn5TV-G>pQmlX9p+ZI+^d`E-*OQi>cVX?gQ)xsU;Jq;`f-O!4)T!(Gf=bb*cwtwbR(jAp>HJB{Fz0K_ zY1zr4>5Fomw)P$E3A{IrDla#7(=|R{>Ljo-577^_FIX69Eihx5>VIsx>4}<>?tM5m zo$iR!m*_0W6>T}gVXoWLYbGP`-1uD|_tN8ajbJ3l6=rkVJS8xDSntMm+O)TRqQE@o zj=o&A;QcjMa!*G{n>oKLd&g#^H=1JfnR^BkW!krxhnP=j1grk_h_ zpEbC~_cq-PG56%>7-FG3q)dAH-i-J2)KRg_Lrrrx?|BMyj*gvUWoq5o>uPU0`&=Kc zy;t)^TlG3sdV!Msz0(}eW_%`z@j-QiG^@8$l2zH+oX40ctF*<94= zj)5_*Ba@aizQAW#N!9J=_G^3TXNI~vW)@`qF}}~!I-88w$FtugMxM|bD(u*(g?#@s zT_5?RcIMz+-+>YrGt@@B7WQ`ih#PuI0{fZQSetWC=9umDGYaNrS~r#T%$yF``LpWq zl}|!ROWT*4q#s9%VrSkVx1`8-%^Ur(M^Q6(i#if=5@;^E6lT_ZdQq-p)%_8lqA;fO z@4l13OWt(HXy~~0Os!Ir?01QmvC8_YJL~-3O6S&;6FUo?tiif zrqi>E+zaLRqBWr0nT-vNL)U&jh0Ar#3CA|(o>M;wE6n-Y8|mA|Lv$~}EUmSpJ;hRf ztM5%rB`?HfEN$*F7b#nlS9+7dXSAJPx9U`68t793k0sWIAwv6H*I)s`sMIdQ+vlE=CWVrRi;$m|A9?IjhCnFHKi zHa&sXW+dut_->w3kQbxl93{6Gj(_j!#;?7L{-<*bEizuc)X!o5oabJ;zhHLIdUI1I zy<+wG=+C`!Uafok!Y3lE=hURGk9=lHE08*Cl$J5q<&O0U?LnQ{dzlsy%0AyZ+Gll) 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 saECM>-&HwHt(}POYX