[GPOS] Keep anchor variation adjustments fractional (#6421)
AnchorFormat3 returned each scaled variation delta as an integer before
subtracting the mark and base anchors. Rounding the two deltas separately
can shift the final attachment by an output unit even with an exact
quarter scalar.
Add float-preserving device queries for anchor resolution. Keep hinting
adjustments and ValueRecord queries on the existing integer path, and
retain the final anchor-difference rounding.
Add ten cases with exact scalars, signed deltas, negative and independent
axis scales. Six fail against the original implementation. The full
meson test suite passes (272 passed, one skipped), as does a reduced
HB_NO_VAR/HB_NO_HINTING compile.
Fixes: #6420
Co-authored-by: OpenAI Codex <noreply@openai.com>
diff --git a/src/OT/Layout/GPOS/AnchorFormat3.hh b/src/OT/Layout/GPOS/AnchorFormat3.hh
index c49705b..1bf4b92 100644
--- a/src/OT/Layout/GPOS/AnchorFormat3.hh
+++ b/src/OT/Layout/GPOS/AnchorFormat3.hh
@@ -40,12 +40,12 @@
if ((font->x_ppem || font->has_nonzero_coords) && xDeviceTable.sanitize (&c->sanitizer, this))
{
hb_barrier ();
- *x += (this+xDeviceTable).get_x_delta (font, c->var_store, c->var_store_cache);
+ *x += (this+xDeviceTable).get_x_delta_f (font, c->var_store, c->var_store_cache);
}
if ((font->y_ppem || font->has_nonzero_coords) && yDeviceTable.sanitize (&c->sanitizer, this))
{
hb_barrier ();
- *y += (this+yDeviceTable).get_y_delta (font, c->var_store, c->var_store_cache);
+ *y += (this+yDeviceTable).get_y_delta_f (font, c->var_store, c->var_store_cache);
}
}
diff --git a/src/hb-ot-layout-common.hh b/src/hb-ot-layout-common.hh
index 19113c3..fa2091b 100644
--- a/src/hb-ot-layout-common.hh
+++ b/src/hb-ot-layout-common.hh
@@ -6288,6 +6288,16 @@
hb_scalar_cache_t *store_cache = nullptr) const
{ return !font->has_nonzero_coords ? 0 : font->em_scalef_y (get_delta (font, store, store_cache)); }
+ float get_x_delta_f (hb_font_t *font,
+ const ItemVariationStore &store,
+ hb_scalar_cache_t *store_cache) const
+ { return !font->has_nonzero_coords ? 0.f : font->em_fscalef_x (get_delta (font, store, store_cache)); }
+
+ float get_y_delta_f (hb_font_t *font,
+ const ItemVariationStore &store,
+ hb_scalar_cache_t *store_cache) const
+ { return !font->has_nonzero_coords ? 0.f : font->em_fscalef_y (get_delta (font, store, store_cache)); }
+
VariationDevice* copy (hb_serialize_context_t *c,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map) const
{
@@ -6350,6 +6360,36 @@
struct Device
{
+ // Anchors retain fractional coordinates until their difference is rounded.
+ // Hinting adjustments and value records keep their integer semantics.
+ float get_x_delta_f (hb_font_t *font,
+ const ItemVariationStore &store,
+ hb_scalar_cache_t *store_cache = nullptr) const
+ {
+#ifndef HB_NO_VAR
+ if (u.b.format == 0x8000)
+ {
+ hb_barrier ();
+ return u.variation.get_x_delta_f (font, store, store_cache);
+ }
+#endif
+ return get_x_delta (font, store, store_cache);
+ }
+
+ float get_y_delta_f (hb_font_t *font,
+ const ItemVariationStore &store,
+ hb_scalar_cache_t *store_cache = nullptr) const
+ {
+#ifndef HB_NO_VAR
+ if (u.b.format == 0x8000)
+ {
+ hb_barrier ();
+ return u.variation.get_y_delta_f (font, store, store_cache);
+ }
+#endif
+ return get_y_delta (font, store, store_cache);
+ }
+
hb_position_t get_x_delta (hb_font_t *font,
const ItemVariationStore &store=Null (ItemVariationStore),
hb_scalar_cache_t *store_cache = nullptr) const
diff --git a/test/shape/data/in-house/fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf b/test/shape/data/in-house/fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf
new file mode 100644
index 0000000..84fe00a
--- /dev/null
+++ b/test/shape/data/in-house/fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf
Binary files differ
diff --git a/test/shape/data/in-house/meson.build b/test/shape/data/in-house/meson.build
index a448f52..9854d2a 100644
--- a/test/shape/data/in-house/meson.build
+++ b/test/shape/data/in-house/meson.build
@@ -1,6 +1,7 @@
in_house_tests = [
'aat-morx.tests',
'aat-trak.tests',
+ 'anchor-variations.tests',
'arabic-fallback-shaping.tests',
'arabic-feature-order.tests',
'arabic-like-joining.tests',
diff --git a/test/shape/data/in-house/tests/anchor-variations.tests b/test/shape/data/in-house/tests/anchor-variations.tests
new file mode 100644
index 0000000..9b7cdfb
--- /dev/null
+++ b/test/shape/data/in-house/tests/anchor-variations.tests
@@ -0,0 +1,12 @@
+@font-funcs=ot
+# Base anchor (100,200), mark anchor (0,0); peak deltas (+1,-1) and (+2,-2).
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;;U+0041,U+0301;[A=0+500|acute=0@-400,200+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=0.25;U+0041,U+0301;[A=0+500|acute=0@-400,200+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=0.5;U+0041,U+0301;[A=0+500|acute=0@-400,201+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=0.75;U+0041,U+0301;[A=0+500|acute=0@-401,201+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=1;U+0041,U+0301;[A=0+500|acute=0@-401,201+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=0.25 --font-size=1500;U+0041,U+0301;[A=0+750|acute=0@-600,300+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=0.5 --font-size=1500;U+0041,U+0301;[A=0+750|acute=0@-601,301+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=0.25 --font-size=-1000;U+0041,U+0301;[A=0+-500|acute=0@400,-200+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=0.5 --font-size=-1000;U+0041,U+0301;[A=0+-500|acute=0@401,-200+0]
+../fonts/a9fff8bd6db3b7426dac8e7eb74431145d5dc38f.ttf;--variations=wght=0.25 --font-size=1000,1500;U+0041,U+0301;[A=0+500|acute=0@-400,300+0]