Undefined drawing behavior after cairo_set_matrix receives a non-invertible matrix
0 reputation · 13 Aug 2022, 11:41 UTC
0 reputation · 13 Aug 2022, 11:41 UTC
Cairo documents that the transformation matrix for cairo_set_matrix must be invertible. When the determinant is zero, the library does not reject, normalize, or raise an API error for the matrix; drawing operations become undefined. The behavior is delegated to each backend rather than resolved inside Cairo, so image, vector, and native surfaces may respond differently to the same degenerate transform. This contract applies across Cairo releases; confirm the exact wording in the reference pages for your version.
A common trigger is a zero width or height from dynamic layout or zoom logic, producing a non-invertible matrix without failure. Although cairo_matrix_invert can detect a non-invertible matrix, cairo_set_matrix performs no validation, leaving callers responsible for guarding against degenerate transforms. Combining a non-invertible matrix with clipping, compositing, or text rendering can amplify backend differences and make failures harder to diagnose.
For teams migrating rendering code across backends, the unresolved decision is whether Cairo should enforce invertibility at set time, clamp degenerate scales, or define a deterministic per-backend error path. Without a settled contract, portable rendering depends on caller-side checks. Should Cairo enforce invertibility inside cairo_set_matrix, or is caller-side validation with cairo_matrix_invert the intended portable contract? Is per-backend behavior for degenerate matrices documented enough for migration? What guard pattern suits dynamic layout code that can produce zero-width or zero-height transforms without dropping content?
35275 reputation · 13 Aug 2022, 17:38 UTC
Cairo does not validate matrix invertibility inside cairo_set_matrix. A matrix with a zero determinant is accepted without error, and the resulting drawing behavior is undefined, delegated to the underlying surface backend. Image, vector, and native surfaces each handle degenerate transforms differently, and no core library normalization or standardized error is produced.
A common trigger is dynamic layout or zoom logic that produces zero width or height, generating a non-invertible matrix silently. While cairo_matrix_invert() can detect such a matrix after the fact, the set operation itself performs no validation, leaving callers responsible for guard logic.
Combining a degenerate matrix with clipping, compositing, or text rendering amplifies backend differences, making failures harder to diagnose across surface types.
cairo_set_matrix, compute the determinant of the 3*2 transform (det = a*d - b*c). If det is exactly zero, either reconstruct the matrix with a non-zero scale or fall back to the identity matrix.cairo_set_matrix, attempt cairo_matrix_invert; a NULL return indicates a degenerate matrix, signaling that subsequent drawing operations may produce undefined output.cairo_image_surface_create and cairo_pdf_surface_create, for example—to observe and document backend-specific behavior.max(computed_width, 0.001)) before constructing the transform, preventing zero-scale matrices from reaching cairo_set_matrix.Without a settled Cairo contract, portable rendering depends on these caller-side checks. Whether the library should enforce invertibility at set time, clamp degenerate scales, or define a deterministic per-backend error path remains an open decision.
Diagnostic detail needed: What Cairo major.minor version are you using, and which surface type(s) does your rendering pipeline target? Backend handling of degenerate matrices and any version-specific guidance may affect whether the above guard pattern is sufficient or if additional backend-specific guards are required.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 13 Aug 2022, 12:48 UTC
When cairo_set_matrix receives a singular matrix (determinant 0), Cairo accepts it without validation, leaving rendering behavior entirely to the backend. Image surfaces typically rasterize a collapsed bitmap, vector paths become empty, and text glyphs may fail to load, producing divergent visual results. Portable code should test invertibility with cairo_matrix_invert() before calling cairo_set_matrix(); if it returns False, substituting an identity matrix or applying a known-safe transform prevents heisenbugs across backends. This guard pattern integrates cleanly into dynamic layout pipelines that may produce zero-width or zero-height scales.