Fix shared use of recording surfaces
While working on PDF Type 3 color fonts (for color fonts with a CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE
) and debugging some issues, I found a problem with the recording surface. The problem is the region data created by _cairo_recording_surface_replay_and_create_regions
and used by _cairo_recording_surface_replay_region
is stored in the recording surface commands. The problem is the recording surface should be immutable as it can be used as a source by multiple targets.
This bug was probably not noticed as in most of the time a recording surface is only created within the paginated surface for one target then destroyed as soon as it has been replayed. When using the recording surface returned by _cairo_scaled_glyph_lookup
, this recording may be used as a source by multiple paginated targets.
I've created a test that demonstrates the problem, however it won't fail in CI due to CI testing one target at a time. If you run the test manually with PDF/PS/SVG enabled you will see it fail due to re-using the same recording surface for multiple targets.
I've done some refactoring to move the region data into a separate cairo_recording_regions_array_t
struct. There may be more than one of these attached to a recording surface. Each instance is identified by a region id. The functions for creating and destroying region arrays are:
cairo_status_t
_cairo_recording_surface_region_array_attach (cairo_surface_t *surface,
unsigned int *id);
Creates a new (empty) region array and returns a unique id for it.
void
_cairo_recording_surface_region_array_remove (cairo_surface_t *surface,
unsigned int id);
Destroy a region array.
_cairo_recording_surface_replay_and_create_regions
takes a region id and creates the regions for it. _cairo_recording_surface_replay_region
takes an region id and replays the regions for it.
The tricky part is nested recording surfaces. The PDF surface may hold a reference to a source surface after the paginated surface has destroyed the recording. The reference function allows the PDF surface to hold a reference to the the region array when is references a recording surface.
void
_cairo_recording_surface_region_array_reference (cairo_surface_t *surface,
unsigned int id);
The only wrinkle is I needed a way to pass the region id to the target during playback when a source is a recording surface. I added a private region id to the pattern to avoid needing to add region ids to the backend operations which would require updating every surface. The wrapper surface makes a temporary copy of the pattern when it needs to read/write the region id field in the pattern so the immutability of the recording surface is not affected.