From 43b6556ce07e51d163c803a0d7451ec27a3131e2 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 23 Feb 2026 07:34:52 +0100 Subject: [PATCH 1/3] Add a test for bug 927 Link: https://gitlab.freedesktop.org/cairo/cairo/-/issues/927 Signed-off-by: Uli Schlachter --- test/bug-927.c | 58 +++++++++++++++++++++++++++++++++ test/meson.build | 1 + test/reference/bug-927.ref.png | Bin 0 -> 643 bytes 3 files changed, 59 insertions(+) create mode 100644 test/bug-927.c create mode 100644 test/reference/bug-927.ref.png diff --git a/test/bug-927.c b/test/bug-927.c new file mode 100644 index 000000000..85003f590 --- /dev/null +++ b/test/bug-927.c @@ -0,0 +1,58 @@ +/* + * Copyright © 2026 Uli Schlachter + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "cairo-test.h" + +static cairo_test_status_t +draw (cairo_t *cr, int width, int height) +{ + double scale = 3; + cairo_matrix_t matrix = { + 1 / scale, 0, + 0, scale, + 0, -100 + }; + + cairo_set_line_width (cr, 20); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_paint (cr); + + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_set_matrix (cr, &matrix); + cairo_move_to (cr, -50, -50); + cairo_line_to (cr, 50, 50); + cairo_line_to (cr, 150, -50); + cairo_stroke (cr); + + return CAIRO_TEST_SUCCESS; +} + +CAIRO_TEST (bug_927, + "Bug 927 (_cairo_matrix_has_unity_scale incorrectly returning true)", + "matrix", /* keywords */ + NULL, /* requirements */ + 30, 100, + NULL, draw) diff --git a/test/meson.build b/test/meson.build index 275bd5e9d..22bb9ec49 100644 --- a/test/meson.build +++ b/test/meson.build @@ -30,6 +30,7 @@ test_sources = [ 'bug-431.c', 'bug-448.c', 'bug-535.c', + 'bug-927.c', 'bug-51910.c', 'bug-75705.c', 'bug-84115.c', diff --git a/test/reference/bug-927.ref.png b/test/reference/bug-927.ref.png new file mode 100644 index 0000000000000000000000000000000000000000..81ece3565a3aaa0c6fa59e1a5a1b404c25feab2c GIT binary patch literal 643 zcmV-}0(||6P)xN%;c?tss82V*$@8&z|eF$eInZL_W&@P&9H&pZujPVndrmeV7~ZL zuh*;9YEcwn1I4u#bR5Un%6h%t>2&rmBk0m9aUh{+Ki4Hg|2+`8H9=^)HJDg|m=-u&f6g@r?dh7+8c#H*HJ*VGrkn$A zG`&f%zOc}=YpgE{XyTqi10gQ_QP(uO`14kbrdt!_nr=-H8puq7-bTQ!5q;sJF>6F$ zLecb=4+@0dm%rF`nx@v)D4?;{Cug%i`$)V`iLz(ww@;p~V7a!*oj}_3{?bdp%fL^Uu@8k7)&0ok; z9LKL`UG#GfAI<0U=ks~JUYpIPb65}v04x>@09Y=Ux7*Fr9i~>Rwb^VYlZi67Ql(0j dDpmRu`Ud0iNd1@mqA~yg002ovPDHLkV1f!OHKzao literal 0 HcmV?d00001 From b892a44a1cb7410b5c4c3c247d630a67c65c6a68 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 23 Feb 2026 07:41:28 +0100 Subject: [PATCH 2/3] Fix _cairo_matrix_has_unity_scale() In commit 1d9f4ae5208d86843a6001d10c9cb5b16df2b785, _cairo_matrix_has_unity_scale() was rewritten. As I understand the commit message, the intention was to allow small rounding errors. In practice however, this function would now also accepts matrices that it should not, such as the matrix in the test added in the previous commit. That matrix has a determinant of 1, but one axis is scaled by 5 and the other one by 1/5. In this commit, I revert the algorithmic change done in the function above. This _cairo_matrix_has_unity_scale() now again compares all the relevant fields of the matrix again without looking at the determinant. To keep the intention of the above commit, this comparison still allows some small inaccuracies. This fixes the unit test added in the previous commit for most backends (xlib*, xcb*, image, recording). The test still fails for svg, script, ps, and image16. Fixes: #927 Signed-off-by: Uli Schlachter --- src/cairo-matrix.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/cairo-matrix.c b/src/cairo-matrix.c index f2be48b74..2ccae0ebd 100644 --- a/src/cairo-matrix.c +++ b/src/cairo-matrix.c @@ -728,6 +728,11 @@ _cairo_matrix_is_integer_translation (const cairo_matrix_t *matrix, #define SCALING_EPSILON _cairo_fixed_to_double(1) +static cairo_bool_t +within_scaling_epsilon(double a, double b) { + return fabs(a - b) < SCALING_EPSILON; +} + /* This only returns true if the matrix is 90 degree rotations or * flips. It appears calling code is relying on this. It will return * false for other rotations even if the scale is one. Approximations @@ -737,21 +742,20 @@ _cairo_matrix_is_integer_translation (const cairo_matrix_t *matrix, cairo_bool_t _cairo_matrix_has_unity_scale (const cairo_matrix_t *matrix) { - /* check that the determinant is near +/-1 */ - double det = _cairo_matrix_compute_determinant (matrix); - if (fabs (det * det - 1.0) < SCALING_EPSILON) { - /* check that one axis is close to zero */ - if (fabs (matrix->xy) < SCALING_EPSILON && - fabs (matrix->yx) < SCALING_EPSILON) - return TRUE; - if (fabs (matrix->xx) < SCALING_EPSILON && - fabs (matrix->yy) < SCALING_EPSILON) - return TRUE; - /* If rotations are allowed then it must instead test for - * orthogonality. This is xx*xy+yx*yy ~= 0. - */ + if (within_scaling_epsilon(matrix->xy, 0.0) && within_scaling_epsilon(matrix->yx, 0.0)) { + if (! (within_scaling_epsilon(matrix->xx, 1.0) || within_scaling_epsilon(matrix->xx, -1.0))) + return FALSE; + if (! (within_scaling_epsilon(matrix->yy, 1.0) || within_scaling_epsilon(matrix->yy, -1.0))) + return FALSE; + } else if (within_scaling_epsilon(matrix->xx, 0.0) && within_scaling_epsilon(matrix->yy, 0.0)) { + if (! (within_scaling_epsilon(matrix->xy, 1.0) || within_scaling_epsilon(matrix->xy, -1.0))) + return FALSE; + if (! (within_scaling_epsilon(matrix->yx, 1.0) || within_scaling_epsilon(matrix->yx, -1.0))) + return FALSE; + } else { + return FALSE; } - return FALSE; + return TRUE; } /* By pixel exact here, we mean a matrix that is composed only of From 8f69b9b3bda9249bbc14904223aac4d90b05cfbf Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 23 Feb 2026 08:26:08 +0100 Subject: [PATCH 3/3] Add more reference images for bug927 Image16 and ps have some slight differences for the outline of the drawn shape. That is fine. script and svg still reproduce the bug and I do not know why. Hence, these are marked as xfail. Signed-off-by: Uli Schlachter --- test/reference/bug-927.image16.ref.png | Bin 0 -> 660 bytes test/reference/bug-927.ps2.ref.png | Bin 0 -> 507 bytes test/reference/bug-927.ps3.ref.png | Bin 0 -> 507 bytes test/reference/bug-927.script.xfail.png | Bin 0 -> 501 bytes test/reference/bug-927.svg11.xfail.png | Bin 0 -> 501 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/reference/bug-927.image16.ref.png create mode 100644 test/reference/bug-927.ps2.ref.png create mode 100644 test/reference/bug-927.ps3.ref.png create mode 100644 test/reference/bug-927.script.xfail.png create mode 100644 test/reference/bug-927.svg11.xfail.png diff --git a/test/reference/bug-927.image16.ref.png b/test/reference/bug-927.image16.ref.png new file mode 100644 index 0000000000000000000000000000000000000000..c6f79441c6e7110f30a1b1369f674d83c9a81434 GIT binary patch literal 660 zcmV;F0&D$=P)^E_VG)+5K9rpSmmW>)fr`r*z53!8YK$c~@v3L`puBr+f zT0OWK;F7)?p#E%U)%^i24pIXsmUP^`>z+^@rY;Ivog@h@-W)xyzgj*?mXl{C)Z+fJ z9Qs15Yf>+MjQRxV*QsS|SX9&ytzl77L$roPOD$OA&*t<&X|?qv-KXT##{E0-MXEMa zJ1EhpU6gZBTdgrr!)T3x8b)hu)COxDUjo(olk|>Gq*m{rcXV;6y`zgm?S+U$?SlyI zwaywdHPqIasiC%}4zg|t7H-^>TDb99sby;#QA1pVM${0kX-ZA4;l60A z8Jhg(MMq5rD)^$UX4LY^H=>qT{@m2enh4Y&%MgJY#F{wN9&1Ej4Am}R@ZYhT+8L|t zi=o=34!*IfPaS+?^#`b3YobxZS&3-WaIA?*ePm6@7hClqrsk8Gp*nxf_tlC|W|o1x z?d$TFT)5I)J(*7S)zfgbE#;!zeb?2$?MJR2J&v~9?P|4>zKr;aVITlTPa^>1=ka>I uu1Uv|B*`qF_3vU&qfMJOZQ8W?7x@5$i1^wJtcVK$0000792(x__`X4X<`Z9^2){;i=|E2x7q6J0}8 zCs|KPE1%91c4 z4FPCiCU5z?);(%m9v3yPyf_z?JGB4+002ovPDHLkV1l~%+-d*- literal 0 HcmV?d00001 diff --git a/test/reference/bug-927.ps3.ref.png b/test/reference/bug-927.ps3.ref.png new file mode 100644 index 0000000000000000000000000000000000000000..1be717f1508bd5d30a798a82f7e882cdf0ab5f25 GIT binary patch literal 507 zcmV792(x__`X4X<`Z9^2){;i=|E2x7q6J0}8 zCs|KPE1%91c4 z4FPCiCU5z?);(%m9v3yPyf_z?JGB4+002ovPDHLkV1l~%+-d*- literal 0 HcmV?d00001 diff --git a/test/reference/bug-927.script.xfail.png b/test/reference/bug-927.script.xfail.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8035eb361bc004ddf6c96e50874bf12b231cab GIT binary patch literal 501 zcmVOD+6q}fRR8rgl;?|-q9EZ!6Qh(Sxhn^bXRp% zcRM7L`b)d|`T5jeD1f1+Y2Jvg*DC;=&u47la=H9+zD)G}em6h-QBf30l9XkM4J_|k z(6`$SyP2lxZnyJC6RrWEEX%5@u+yb2F}kkn{eF*~I$ZaC|9ZW!%g5s}%Q8>&JkO8E z1G{`WovkH2o$qwQW01(?`&&TjD@M(SEL1 zhW)&FQI69<%0sD_wpaR9)`i% r8oxgvR4SE9rBbO>DwRs50s#01r82`?)9ivT00000NkvXXu0mjfYhT~O literal 0 HcmV?d00001 diff --git a/test/reference/bug-927.svg11.xfail.png b/test/reference/bug-927.svg11.xfail.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8035eb361bc004ddf6c96e50874bf12b231cab GIT binary patch literal 501 zcmVOD+6q}fRR8rgl;?|-q9EZ!6Qh(Sxhn^bXRp% zcRM7L`b)d|`T5jeD1f1+Y2Jvg*DC;=&u47la=H9+zD)G}em6h-QBf30l9XkM4J_|k z(6`$SyP2lxZnyJC6RrWEEX%5@u+yb2F}kkn{eF*~I$ZaC|9ZW!%g5s}%Q8>&JkO8E z1G{`WovkH2o$qwQW01(?`&&TjD@M(SEL1 zhW)&FQI69<%0sD_wpaR9)`i% r8oxgvR4SE9rBbO>DwRs50s#01r82`?)9ivT00000NkvXXu0mjfYhT~O literal 0 HcmV?d00001