From 321d5588d4fa96bcc4aa558d2f982430031f242e Mon Sep 17 00:00:00 2001 From: Qunxin Liu Date: Tue, 2 Jul 2019 16:30:57 -0700 Subject: [PATCH] [subset] Add subsetting for GPOS Lookup Type 1: Single Adjustment Positioning Subtable --- src/hb-ot-layout-gpos-table.hh | 157 ++++++++++++++++++++- test/subset/data/Makefile.am | 1 + test/subset/data/Makefile.sources | 1 + .../gpos1_2_font.keep-layout-retain-gids.41,43.otf | Bin 0 -> 2068 bytes .../gpos1_2_font.keep-layout-retain-gids.41,46.otf | Bin 0 -> 2232 bytes .../gpos1_2_font.keep-layout-retain-gids.43,46.otf | Bin 0 -> 2096 bytes ...eep-layout-retain-gids.retain-all-codepoint.otf | Bin 0 -> 3668 bytes test/subset/data/fonts/gpos1_2_font.otf | Bin 0 -> 4564 bytes test/subset/data/tests/layout.gpos.tests | 11 ++ 9 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 test/subset/data/expected/layout.gpos/gpos1_2_font.keep-layout-retain-gids.41,43.otf create mode 100644 test/subset/data/expected/layout.gpos/gpos1_2_font.keep-layout-retain-gids.41,46.otf create mode 100644 test/subset/data/expected/layout.gpos/gpos1_2_font.keep-layout-retain-gids.43,46.otf create mode 100644 test/subset/data/expected/layout.gpos/gpos1_2_font.keep-layout-retain-gids.retain-all-codepoint.otf create mode 100644 test/subset/data/fonts/gpos1_2_font.otf create mode 100644 test/subset/data/tests/layout.gpos.tests diff --git a/src/hb-ot-layout-gpos-table.hh b/src/hb-ot-layout-gpos-table.hh index 5c21980..7d57056 100644 --- a/src/hb-ot-layout-gpos-table.hh +++ b/src/hb-ot-layout-gpos-table.hh @@ -236,6 +236,11 @@ struct ValueFormat : HBUINT16 } }; +template +static inline void SinglePos_serialize (hb_serialize_context_t *c, + Iterator it, + ValueFormat valFormat); + struct AnchorFormat1 { @@ -496,11 +501,52 @@ struct SinglePosFormat1 return_trace (true); } + template + void serialize (hb_serialize_context_t *c, + Iterator it, + ValueFormat valFormat) + { + if (unlikely (!c->extend_min (*this))) return; + if (unlikely (!c->check_assign (valueFormat, valFormat))) return; + + auto vals = hb_second (*it); + + + vals + | hb_apply ([=] (const Value& _) + { + c->copy (_); + }) + ; + + auto glyphs = + + it + | hb_map_retains_sorting (hb_first) + ; + + coverage.serialize (c, this).serialize (c, glyphs); + } + bool subset (hb_subset_context_t *c) const { TRACE_SUBSET (this); - // TODO(subset) - return_trace (false); + const hb_set_t &glyphset = *c->plan->glyphset_gsub (); + const hb_map_t &glyph_map = *c->plan->glyph_map; + + unsigned length = valueFormat.get_len (); + + auto it = + + hb_iter (this+coverage) + | hb_filter (glyphset) + | hb_map_retains_sorting ([&] (hb_codepoint_t p) + { + return hb_pair (glyph_map[p], values.as_array (length)); + }) + ; + + bool ret = bool (it); + SinglePos_serialize (c->serializer, it, valueFormat); + return_trace (ret); } bool sanitize (hb_sanitize_context_t *c) const @@ -552,11 +598,58 @@ struct SinglePosFormat2 return_trace (true); } + template + void serialize (hb_serialize_context_t *c, + Iterator it, + ValueFormat valFormat) + { + if (unlikely (!c->extend_min (*this))) return; + if (unlikely (!c->check_assign (valueFormat, valFormat))) return; + if (unlikely (!c->check_assign (valueCount, it.len ()))) return; + + + it + | hb_map (hb_second) + | hb_apply ([=] (hb_array_t val_iter) + { + + val_iter + | hb_apply ([=] (const Value& _) + { + c->copy (_); + }) + ; + }) + ; + + auto glyphs = + + it + | hb_map_retains_sorting (hb_first) + ; + + coverage.serialize (c, this).serialize (c, glyphs); + } + bool subset (hb_subset_context_t *c) const { TRACE_SUBSET (this); - // TODO(subset) - return_trace (false); + const hb_set_t &glyphset = *c->plan->glyphset_gsub (); + const hb_map_t &glyph_map = *c->plan->glyph_map; + + unsigned sub_length = valueFormat.get_len (); + unsigned total_length = (unsigned)valueCount * sub_length; + + auto it = + + hb_zip (this+coverage, hb_range ((unsigned) valueCount)) + | hb_filter (glyphset, hb_first) + | hb_map_retains_sorting ([&] (const hb_pair_t& _) + { + return hb_pair (glyph_map[_.first], values.as_array (total_length).sub_array (_.second * sub_length, sub_length)); + }) + ; + + bool ret = bool (it); + SinglePos_serialize (c->serializer, it, valueFormat); + return_trace (ret); } bool sanitize (hb_sanitize_context_t *c) const @@ -583,6 +676,55 @@ struct SinglePosFormat2 struct SinglePos { + template + unsigned get_format (Iterator glyph_val_iter_pairs) + { + unsigned subset_format = 1; + hb_array_t first_val_iter = hb_second (*glyph_val_iter_pairs); + + + glyph_val_iter_pairs + | hb_map (hb_second) + | hb_apply ([&] (hb_array_t val_iter) + { + + hb_zip (val_iter, first_val_iter) + | hb_apply ([&] (const hb_pair_t& _) + { + if (_.first != _.second) + { + subset_format = 2; + return; + } + }) + ; + }) + ; + + return subset_format; + } + + + template + void serialize (hb_serialize_context_t *c, + Iterator glyph_val_iter_pairs, + ValueFormat valFormat) + { + if (unlikely (!c->extend_min (u.format))) return; + unsigned format = 2; + + if (glyph_val_iter_pairs) format = get_format (glyph_val_iter_pairs); + + u.format = format; + switch (u.format) { + case 1: u.format1.serialize (c, glyph_val_iter_pairs, valFormat); + return; + case 2: u.format2.serialize (c, glyph_val_iter_pairs, valFormat); + return; + default:return; + } + } + template typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const { @@ -603,6 +745,13 @@ struct SinglePos } u; }; +template +static inline void +SinglePos_serialize (hb_serialize_context_t *c, + Iterator it, + ValueFormat valFormat) +{ c->start_embed ()->serialize (c, it, valFormat); } + struct PairValueRecord { diff --git a/test/subset/data/Makefile.am b/test/subset/data/Makefile.am index 618e663..1e90367 100644 --- a/test/subset/data/Makefile.am +++ b/test/subset/data/Makefile.am @@ -13,6 +13,7 @@ EXTRA_DIST += \ expected/japanese \ expected/cff-japanese \ expected/layout \ + expected/layout.gpos \ fonts \ profiles \ $(NULL) diff --git a/test/subset/data/Makefile.sources b/test/subset/data/Makefile.sources index f065e83..8bacc51 100644 --- a/test/subset/data/Makefile.sources +++ b/test/subset/data/Makefile.sources @@ -5,6 +5,7 @@ TESTS = \ tests/japanese.tests \ tests/cff-japanese.tests \ tests/layout.tests \ + tests/layout.gpos.tests \ $(NULL) XFAIL_TESTS = \ diff --git a/test/subset/data/expected/layout.gpos/gpos1_2_font.keep-layout-retain-gids.41,43.otf b/test/subset/data/expected/layout.gpos/gpos1_2_font.keep-layout-retain-gids.41,43.otf new file mode 100644 index 0000000000000000000000000000000000000000..6b2879fff291950c0185f8d46acf5bbb890e4244 GIT binary patch literal 2068 zcmd^AZBUd|6h7~=EX%ThC`*cp3rInd?)$lbpU8rl8V1^67P!H35LrHU7X=4O6BJoM zEYi?4$hFD9a7>IDo1By}vYHz0W1L}(@sEG1F^Ag4SEKXp=EpSs>u2Yk_nz}S=iYnH z{g`ue*R9J19qfQ0FlOcC7=Mafa{~a*0I1C?*5>Aqb==GV2pJ)6b#DFwuHviprvNks zgf|u2t<`W`b%FdA!X;%kYst_raocF@&xEBiBBqRM$gdFy&n>gN>Y}=WngD|50x(nT z*1Br2tR)xp)32$t+HIQ3xHvM02)9;OIbD~p&Vtv+Nnz=z}Ey|U)crHK{6zWT_~3By!hV-#f5 zRJ8bD2!T)tgK&t1D3}H@Fau`697up9NQQYZA6O8;3>mN(mO&Qez)DyRxsV6zp#U~Q zAy}aV5X!+0)!>A!P!A38D!c~G&;oAgfc?+~2jLJLfgU&p@4*RL^Z=ZOK^TH_@F{%8 zsF_e?kwYE@6j4GmreQj=CS+M;Ib?Zc1!P5JC1lOWrXib-oC!G^!y(5bCm<&xCn0A> zE)BVK_rlVv+iA9M+iAPC5iSm_DGG|p)*E=?2nah}5oMPlmCeD~yQuVUU zm|yR7+3Zf^%F5y@M|G9M>avy0H)d8;81t0wPGg?UX>)8PGH|GLs(Y4$g$_AH8>$P_ zhet$CiJCes+7R>D^ck@;XU&eAV~kHooSXFc6Uix0&P#o2{sI%r@q#Fs)6z2*E?T@~ z>9Wk_S(faaRjZ#%)FtX7{;S06j5JBSEX^@>#75^%Y^bWQsX2P~l$o5IRH72fq)!nIJWFT9s6%OdEa8By zf(SwZJLnN!{fHe5(Nxp}eKdX^sc2VG*=C`L-kTyQB~%G6!g*k$vR(@nRNx)(JRt}5 z+wAwV^emYCxu}%c$ciSdBfSXJ6hX;RO}t83jgtQZ{cz2CV!_{6(aMceR(Jo;B0KO0 zkwr53w(=hSizv}`5C^HiR9Ib=pryzp6jkvsE}PRuTp%tzpaGT`73h@=C)57??tZ3! E0S4ybX8-^I literal 0 HcmV?d00001 diff --git a/test/subset/data/expected/layout.gpos/gpos1_2_font.keep-layout-retain-gids.41,46.otf b/test/subset/data/expected/layout.gpos/gpos1_2_font.keep-layout-retain-gids.41,46.otf new file mode 100644 index 0000000000000000000000000000000000000000..eebb3e158975d281cfd5420154217afdf84efa08 GIT binary patch literal 2232 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 P7?cbj-F{U0ccy;Lel2+^e^apXYPCVzW00U z<>TF@ue`DpBA^O_LA-GBV(}-JcdG!PA0XoLvo9}Qek2xr3nsQY3K@_Mc~A%?PzDvS8LGhs4yc2A@Iw=9gEr`ZPS^!+!fv+j zJ~#k}U;vK62{;L7;2d0pD{vLA!AEcd2H_UmhR@-z@Fjc$-@!fL;YYYnfCQ5;5>BE> zG5b=sxnfwf~u8N&4yG{wT7y-RIQ_GJyjd1+DJ7Ds!>vn zifY)58miG!jgD&cRAZnTBh@OXR!Oxgs#Q}hTacD&byTaTS_9P@sZK$4N~%*)oto-2 zRL9n$SHEO6)ON}~e)Keon4CBJwyt0A~G`&bHOO=aN3WZ9XoR<5# zS-iZgu*jTUD1JUCHMbxyx4=|nPJ2?EoRuYBhTRLr%glx5f)ZvN^E>8v_SYc|ohOXB z%|sBv6y`1oAZM9|p%stkDnYl(T6fw7`gc{}&9%x|h&<hHi;?Am_-fn9khiD1_~i9E)xI-bbLOrj!sGKVCS#pFfu zGFd@hBPr+tB!?7|Qeq+1#7^AAPnt=9bdop79@0np$q{mboF?bV74iZ3m<*D?kT1xW zFBuGe*kRTyJLV|<@2?-JsBqT`0BN2~8JQDFp#3K=pL_8AlNW>!%k3<3z2}mR$ zk$^-35(!8oAd!GX0ul*GNRf~tAw@!pgcJ!W5>h0jNJx>8B9VwhA`*#6BqEWBL?RN2 zNF*YWh(sb1Nk}9ik%UAN5=lrTA(4bc5)w&BBq1R~LWYD42^kVHBxFd)kdPrELqdjx z90@rRawOzP$dQmEAxA=vgd7Pu5(VAE(r&@t%`a*>)h#r4hw**gBd5Esb-&O(?!Vi( z|FnhoM0^_A6BYHTFKRsdl7^p|G%7`dk3b~;MQni{GMVfRnjds7`03!rkd>j)p_6#& z@Jvzix?EwlZT=QvVyBSD%Tl+YGIBut2x`3<=f(`-CBFR;@!sM?#u2g^_SiI z8|n_FH*;}L0jy%Lv!kZN(;ndC_-&`I^5NI`3D5BoL6N%BvL=63X{sf#s=txrhevuv zk#~l!i(33G4Xxgj0sbX-(`wM z^Tn^9?z6ic9=oT#A<)#e?fUu5ALblX92Le@D^j_$CZ5ywxtwW^>r>)0{3$oUmzRhN!94-`dm?*tP#ed)Ki`$1fbe z@Rr0a1la|msTO`!xq1gT6f5M4BKwCYi`?uuTvMzbtFJb;ZSf+>`L!Rc<}=P66S$_0 z^@Wb&N@3EfbRo)Sb}gyR-<#8wksHW3uL3FY8Ld7K~*R|8_>6%uUQEDzW>je4Jg1l5n_O3r{-?n!_2>~Vt;w|N9a%m-Zcu5I=fS5wU!Wi4iZ?yiJ& zGli%?eA@zE{K?Hdy)RW+9)6X`v!;4Y#`^3Hx=S_vRi{7Xr@mj*=xDSx z*gRR@EKfGaFCX!-`-mQn7Of7f>0ap<=I+cC8V6@wnOE-p%E|eg@SD=vxjhhQZ+yY~ zf^CUCqk2WE<@utyM_24fZ7E&7zx2qaflUMDd#d(s>+$@x}4_Y{vlX<+;QeRPRFX8wVBgfdf9d)8e_Nl}NEzFv5;$lkl zobTCx`9WrZ3hDkTEN^7UZo}jU^&zUDgG>#EAhfcl>mZr&|B=~d*#5)Mu<|_a3c5m{ z8a)-<=Ff>hQ1}_*V{h~*a*y0&Z(!AE39Qa)06`I9!C|41K|vu5!884LN?x?!8TMW$ zkUBO8%1`H!2M-?LtV~O08B-Yinw>8R31@w$Fnz2Z1QVDxRuBG#J>(blP?*Fbgt0zh zqjt(7fLs;>C?SQ#FDbB|X*ular)1HBnZ*djkj0{f0`@R8Gg$~5M%$I_;iwp0OAwC6 zYLi$Q!AcE;fS8pbETcUYR}X9xDA|vNSfg_4s2sD6_G3E6Hlyi5JH}G~BVo@7h=mzI uvP?xez_D3aVxwaVDKZxpG273hj;(+&3!V)d{ej`1AO16Mbe?34i~a?7TV<^P literal 0 HcmV?d00001 diff --git a/test/subset/data/fonts/gpos1_2_font.otf b/test/subset/data/fonts/gpos1_2_font.otf new file mode 100644 index 0000000000000000000000000000000000000000..28331f25910c76107c151b6c75f09561a1c9c3e9 GIT binary patch literal 4564 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