Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CairoMakie: Allow restricting PDF version #3845

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## [Unreleased]
- CairoMakie: Add argument `pdf_version` to restrict the PDF version when saving a figure as a PDF [#3845](https://github.com/MakieOrg/Makie.jl/pull/3845).

## [0.21.0] - 2024-05-21

Expand Down
7 changes: 7 additions & 0 deletions CairoMakie/src/cairo-extension.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,10 @@ function get_render_type(surface::Cairo.CairoSurface)
typ == Cairo.CAIRO_SURFACE_TYPE_IMAGE && return IMAGE
return IMAGE # By default assume that the render type is IMAGE
end

function restrict_pdf_version!(surface::Cairo.CairoSurface, v::Integer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be simpler to type the v with the enum here, then you don't need yet another check. I'd guess you don't even have to manually convert it to Int32 in the ccall

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to move restrict_pdf_version! to Cairo.jl eventually, where they stick to numerical constants instead of enums, so I'd prefer to keep the argument as Integer.

You are right that the manual conversion in the ccall is not needed. I removed it.

@assert surface.ptr != C_NULL
0 ≤ v ≤ 3 || throw(ArgumentError("version must be 0, 1, 2, or 3 (received $v)"))
ccall((:cairo_pdf_surface_restrict_to_version, Cairo.libcairo), Nothing,
(Ptr{UInt8}, Int32), surface.ptr, v)
end
28 changes: 28 additions & 0 deletions CairoMakie/src/screen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ function surface_from_output_type(type::RenderType, io, w, h)
end
end

@enum PDFVersion PDFv14 PDFv15 PDFv16 PDFv17
function pdfversion(version::AbstractString)
version == "1.4" && return PDFv14
version == "1.5" && return PDFv15
version == "1.6" && return PDFv16
version == "1.7" && return PDFv17
throw(ArgumentError("PDF version must be one of '1.4', '1.5', '1.6', '1.7' (received '$version')"))
end

"""
Supported options: `[:best => Cairo.ANTIALIAS_BEST, :good => Cairo.ANTIALIAS_GOOD, :subpixel => Cairo.ANTIALIAS_SUBPIXEL, :none => Cairo.ANTIALIAS_NONE]`
"""
Expand All @@ -85,13 +94,22 @@ to_cairo_antialias(aa::Int) = aa
* `pt_per_unit = 0.75`
* `antialias::Union{Symbol, Int} = :best`: antialias modus Cairo uses to draw. Applicable options: `[:best => Cairo.ANTIALIAS_BEST, :good => Cairo.ANTIALIAS_GOOD, :subpixel => Cairo.ANTIALIAS_SUBPIXEL, :none => Cairo.ANTIALIAS_NONE]`.
* `visible::Bool`: if true, a browser/image viewer will open to display rendered output.
* `pdf_version::String = nothing`: the version of output PDFs. Applicable options are `"1.4"`, `"1.5"`, `"1.6"`, `"1.7"`, or `nothing`, which leaves the PDF version unrestricted.
"""
struct ScreenConfig
px_per_unit::Float64
pt_per_unit::Float64
antialias::Symbol
visible::Bool
start_renderloop::Bool # Only used to satisfy the interface for record using `Screen(...; start_renderloop=false)` for GLMakie
pdf_version::Union{Nothing, PDFVersion}

function ScreenConfig(px_per_unit::Real, pt_per_unit::Real,
antialias::Symbol, visible::Bool, start_renderloop::Bool,
pdf_version::Union{Nothing, AbstractString})
v = isnothing(pdf_version) ? nothing : pdfversion(pdf_version)
new(px_per_unit, pt_per_unit, antialias, visible, start_renderloop, v)
end
end

function device_scaling_factor(rendertype, sc::ScreenConfig)
Expand Down Expand Up @@ -213,6 +231,11 @@ function apply_config!(screen::Screen, config::ScreenConfig)
aa = to_cairo_antialias(config.antialias)
Cairo.set_antialias(context, aa)
set_miter_limit(context, 2.0)

if get_render_type(surface) === PDF && !isnothing(config.pdf_version)
restrict_pdf_version!(surface, Int(config.pdf_version))
end

screen.antialias = aa
screen.device_scaling_factor = dsf
screen.config = config
Expand Down Expand Up @@ -285,6 +308,11 @@ function Screen(scene::Scene, config::ScreenConfig, surface::Cairo.CairoSurface)
aa = to_cairo_antialias(config.antialias)
Cairo.set_antialias(ctx, aa)
set_miter_limit(ctx, 2.0)

if get_render_type(surface) === PDF && !isnothing(config.pdf_version)
restrict_pdf_version!(surface, Int(config.pdf_version))
end

return Screen{get_render_type(surface)}(scene, surface, ctx, dsf, aa, config.visible, config)
end

Expand Down
34 changes: 34 additions & 0 deletions CairoMakie/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,37 @@ functions = [:volume, :volume!, :uv_mesh]
missing_images, scores = ReferenceTests.record_comparison(recording_dir)
ReferenceTests.test_comparison(scores; threshold = 0.05)
end

@testset "PdfVersion" begin
@test CairoMakie.pdfversion("1.4") === CairoMakie.PDFv14
@test CairoMakie.pdfversion("1.5") === CairoMakie.PDFv15
@test CairoMakie.pdfversion("1.6") === CairoMakie.PDFv16
@test CairoMakie.pdfversion("1.7") === CairoMakie.PDFv17
@test_throws ArgumentError CairoMakie.pdfversion("foo")
end

@testset "restrict PDF version" begin
magic_number(filename) = open(filename) do f
return String(read(f, sizeof("%PDF-X.Y")))
end

filename = "$(tempname()).pdf"

try
save(filename, Figure(), pdf_version=nothing)
@test startswith(magic_number(filename), "%PDF-")
finally
rm(filename)
end

for version in ["1.4", "1.5", "1.6", "1.7"]
try
save(filename, Figure(), pdf_version=version)
@test magic_number(filename) == "%PDF-$version"
finally
rm(filename)
end
end

@test_throws ArgumentError save(filename, Figure(), pdf_version="foo")
end
10 changes: 10 additions & 0 deletions docs/explanations/backends/cairomakie.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,13 @@ v = rand(10,2)
scatter(v[:,1], v[:,2], rasterize = 10, markersize = 30.0)
```
\end{examplefigure}

#### PDF version

The version of output PDFs can be restricted via the `pdf_version` argument of the screen config. Conveniently, it can be also passed as an argument of the `save` function:
```julia
using CairoMakie
fig = Figure()
# ...
save("figure.pdf", fig, pdf_version="1.4")
```
3 changes: 2 additions & 1 deletion src/theming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ const MAKIE_DEFAULT_THEME = Attributes(
pt_per_unit = 0.75,
antialias = :best,
visible = true,
start_renderloop = false
start_renderloop = false,
pdf_version = nothing
),

GLMakie = Attributes(
Expand Down