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

fix conditional dependencies #312

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ to simplify the creation of complex graphics and serves as the basis of the

## Package features

- Renders publication quality graphics to SVG, PNG, Postscript, PDF and PGF
- Intuitive and consistent interface
- Renders publication quality graphics to PGF and interactive SVG using pure Julia; relies on [cairo](https://cairographics.org/) for PNG, Postscript, PDF and PGF
- Can handle most text formatting natively; calls out to [pango](https://www.pango.org/) for more advanced notation and [fontconfig](https://www.fontconfig.org/) for a wider choice of fonts
- Works with [Jupyter](http://jupyter.org/) notebooks via [IJulia](https://github.com/JuliaLang/IJulia.jl) out of the box

## Installation
Expand Down
9 changes: 9 additions & 0 deletions docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ composition2 |> SVG("celery.svg")
composition3 |> SVG("rutabaga.svg") # etc...
```

To render to PDF, PNG, and postscript, first install, if necessary,
and load Cairo before instantiating a backend:

```
Pkg.add("Cairo")
import Cairo
composition |> PDF("tomato.pdf")
```


## The compose function accepts S-expressions

Expand Down
26 changes: 15 additions & 11 deletions src/Compose.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ default_fill_color = colorant"black"
# Use cairo for the PNG, PS, PDF if it's installed.
macro missing_cairo_error(backend)
msg1 = """
The Cairo and Fontconfig packages are necessary for the $(backend) backend.
Add them with the package manager if necessary, then run:
import Cairo, Fontconfig
The Cairo package is necessary for the $(backend) backend.
Add it with the package manager if necessary, then run:
import Cairo
before invoking $(backend).
"""
string(msg1)
Expand All @@ -139,22 +139,25 @@ PDF(args...; kwargs...) = error(@missing_cairo_error "PDF")
include("svg.jl")
include("pgf_backend.jl")

# If available, pango and fontconfig are used to compute text extents and match
# If imported, pango and/or fontconfig are used to compute text extents and match
# fonts. Otherwise a simplistic pure-julia fallback is used.

include("fontfallback.jl")

function link_fontconfig()
@info "Loading Fontconfig backend into Compose.jl"
pango_cairo_ctx = C_NULL
include("fontconfig.jl")
end

function link_pango()
@info "Loading Pango backend into Compose.jl"
global pango_cairo_ctx = C_NULL
include("pango.jl")

ccall((:g_type_init, libgobject), Cvoid, ())
pango_cairo_fm = ccall((:pango_cairo_font_map_new, libpangocairo),
Ptr{Cvoid}, ())
pango_cairo_ctx = ccall((:pango_font_map_create_context, libpango),
Ptr{Cvoid}, (Ptr{Cvoid},), pango_cairo_fm)
pangolayout = PangoLayout()
#Pango.g_type_init()
pango_cairo_fm = Pango.pango_cairo_font_map_new()
pango_cairo_ctx = Pango.pango_font_map_create_context(pango_cairo_fm)
pangolayout = Base.invokelatest(PangoLayout)
end

function link_cairo()
Expand All @@ -166,6 +169,7 @@ end
function __init__()
@require Cairo="159f3aea-2a34-519c-b102-8c37f9878175" link_cairo()
@require Fontconfig="186bb1d3-e1f7-5a2c-a377-96d770f13627" link_fontconfig()
@require Pango="4071c42e-c4ca-11e8-2c78-e3c1daf35efa" link_pango()
end

show(io::IO, m::MIME"text/html", ctx::Context) =
Expand Down
8 changes: 7 additions & 1 deletion src/abandoned.jl
Original file line number Diff line number Diff line change
Expand Up @@ -812,5 +812,11 @@ function draw(img::SVG, prim::PathPrimitive, idx::Int)
print(img.out, "/>\n")
end


function current_point(img::Image)
x = Array{Float64}(undef, 1)
y = Array{Float64}(undef, 1)
ccall((:cairo_get_current_point, Cairo._jl_libcairo), Cvoid,
(Ptr{Cvoid}, Ptr{Float64}, Ptr{Float64}), img.ctx.ptr, x, y)
return ((x[1] / img.ppmm)*mm, (x[2] / img.ppmm)*mm)
end

8 changes: 0 additions & 8 deletions src/cairo_backends.jl
Original file line number Diff line number Diff line change
Expand Up @@ -457,14 +457,6 @@ apply_property(img::Image, property::SVGAttributePrimitive) = nothing
# Cairo Wrappers
# --------------

function current_point(img::Image)
x = Array{Float64}(undef, 1)
y = Array{Float64}(undef, 1)
ccall((:cairo_get_current_point, Cairo._jl_libcairo), Cvoid,
(Ptr{Cvoid}, Ptr{Float64}, Ptr{Float64}), img.ctx.ptr, x, y)
return ((x[1] / img.ppmm)*mm, (x[2] / img.ppmm)*mm)
end

move_to(img::Image, point::AbsoluteVec2) = Cairo.move_to(img.ctx,
absolute_native_units(img, point[1].value),
absolute_native_units(img, point[2].value))
Expand Down
30 changes: 30 additions & 0 deletions src/fontconfig.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Use the freetype/fontconfig backend to find the best match to a font
# description.
#
# Args:
# desc: A string giving the font description. This can
# also provide a comma-separated list of families. E.g.,
# "Helvetica, Arial 10"
#
# Returns:
# A pointer to a PangoFontDescription with the closest match.
#
let available_font_families = Set{AbstractString}()
for font_pattern in Fontconfig.list()
push!(available_font_families, lowercase(Fontconfig.format(font_pattern, "%{family}")))
end

meta_families = Set(["serif", "sans", "sans-serif", "monospace", "cursive", "fantasy"])

global match_font
function match_font(families::AbstractString, size::Float64)
matched_family = "sans-serif"
for family in [lowercase(strip(family, [' ', '"', '\''])) for family in split(families, ',')]
if family in available_font_families || family in meta_families
matched_family = family
break
end
end
Fontconfig.format(match(Fontconfig.Pattern(family=family)), "%{family}")
end
end
99 changes: 24 additions & 75 deletions src/pango.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# Estimation of text extents using pango.

const libpangocairo = Cairo._jl_libpangocairo
const libpango = Cairo._jl_libpango
const libgobject = Cairo._jl_libgobject

# Cairo text backend
const CAIRO_FONT_TYPE_TOY = 0
const CAIRO_FONT_TYPE_FT = 1
Expand All @@ -16,58 +12,26 @@ const PANGO_SCALE = 1024.0

pango_fmt_float(x::Float64) = @sprintf("%0.4f", x)

# Use the freetype/fontconfig backend to find the best match to a font
# description.
#
# Args:
# desc: A string giving the font description. This can
# also provide a comma-separated list of families. E.g.,
# "Helvetica, Arial 10"
#
# Returns:
# A pointer to a PangoFontDescription with the closest match.
#
let available_font_families = Set{AbstractString}()
for font_pattern in Fontconfig.list()
push!(available_font_families, lowercase(Fontconfig.format(font_pattern, "%{family}")))
end

meta_families = Set(["serif", "sans", "sans-serif", "monospace", "cursive", "fantasy"])

global match_font
function match_font(families::AbstractString, size::Float64)
matched_family = "sans-serif"
for family in [lowercase(strip(family, [' ', '"', '\''])) for family in split(families, ',')]
if family in available_font_families || family in meta_families
matched_family = family
break
end
end
family = Fontconfig.format(match(Fontconfig.Pattern(family=family)), "%{family}")
desc = @sprintf("%s %fpx", family, size)
fd = ccall((:pango_font_description_from_string, libpango), Ptr{Cvoid}, (Ptr{UInt8},), desc)
return fd
end
end

# Thin wrapper for a pango_layout object.
mutable struct PangoLayout
layout::Ptr{Cvoid}
end

function PangoLayout()
layout = ccall((:pango_layout_new, libpango),
Ptr{Cvoid}, (Ptr{Cvoid},), pango_cairo_ctx)
layout = Pango.pango_layout_new(pango_cairo_ctx)
# TODO: finalizer?

PangoLayout(layout)
end

# Set the layout's font.
function pango_set_font(pangolayout::PangoLayout, family::AbstractString, pts::Number)
fd = match_font(family, pts)
ccall((:pango_layout_set_font_description, libpango),
Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), pangolayout.layout, fd)
family = match_font(family, pts)

desc = @sprintf("%s %fpx", family, size)
fd = Pango.pango_font_description_from_string(desc)

Pango.pango_layout_set_font_description(pangolayout.layout, fd)
end

# Find the width and height of a string.
Expand All @@ -81,14 +45,10 @@ end
#
function pango_text_extents(pangolayout::PangoLayout, text::AbstractString)
textarray = convert(Vector{UInt8}, convert(String, text))
ccall((:pango_layout_set_markup, libpango),
Cvoid, (Ptr{Cvoid}, Ptr{UInt8}, Int32),
pangolayout.layout, textarray, length(textarray))
Pango.pango_layout_set_markup(pangolayout.layout, textarray, length(textarray))

extents = Array{Int32}(undef, 4)
ccall((:pango_layout_get_extents, libpango),
Cvoid, (Ptr{Cvoid}, Ptr{Int32}, Ptr{Int32}),
pangolayout.layout, extents, C_NULL)
Pango.pango_layout_get_extents(pangolayout.layout, extents, C_NULL)

width, height = (extents[3] / PANGO_SCALE)pt, (extents[4] / PANGO_SCALE)pt
end
Expand All @@ -106,11 +66,11 @@ end
# A (width, height) tuple in absolute units.
#
function max_text_extents(font_family::AbstractString, pts::Float64, texts::AbstractString...)
pango_set_font(pangolayout::PangoLayout, font_family, pts)
Pango.pango_set_font(pangolayout::PangoLayout, font_family, pts)
max_width = 0mm
max_height = 0mm
for text in texts
(width, height) = pango_text_extents(pangolayout::PangoLayout, text)
(width, height) = Pango.pango_text_extents(pangolayout::PangoLayout, text)
max_width = max_width.value < width.value ? width : max_width
max_height = max_height.value < height.value ? height : max_height
end
Expand All @@ -126,8 +86,8 @@ end

# Return an array with the extents of each element
function text_extents(font_family::AbstractString, pts::Float64, texts::AbstractString...)
pango_set_font(pangolayout::PangoLayout, font_family, pts)
return [pango_text_extents(pangolayout::PangoLayout, text) for text in texts]
Pango.pango_set_font(pangolayout::PangoLayout, font_family, pts)
return [Pango.pango_text_extents(pangolayout::PangoLayout, text) for text in texts]
end

text_extents(font_family::AbstractString, size::Measure, texts::AbstractString...) =
Expand Down Expand Up @@ -272,23 +232,17 @@ unpack_pango_float(ptr::Ptr{Cvoid}) = unsafe_wrap(Array, convert(Ptr{Float64}, p
# should be applied starting at that position.
#
function unpack_pango_attr_list(ptr::Ptr{Cvoid})
attr_it = ccall((:pango_attr_list_get_iterator, libpango),
Ptr{Cvoid}, (Ptr{Cvoid},), ptr)
attr_it = Pango.pango_attr_list_get_iterator(ptr)

# Alias some ugly C calls.
attr_it_next = () -> ccall((:pango_attr_iterator_next, libpango),
Int32, (Ptr{Cvoid},), attr_it)
attr_it_next = () -> Pango.pango_attr_iterator_next(attr_it)

attr_it_get = attr_name -> ccall((:pango_attr_iterator_get, libpango),
Ptr{Cvoid}, (Ptr{Cvoid}, Int32),
attr_it, eval(attr_name))
attr_it_get = attr_name -> Pango.pango_attr_iterator_get(attr_it, eval(attr_name))

attr_it_range = () -> begin
start_idx = Array{Int32}(undef, 1)
end_idx = Array{Int32}(undef, 1)
ccall((:pango_attr_iterator_range, libpango),
Cvoid, (Ptr{Cvoid}, Ptr{Int32}, Ptr{Int32}),
attr_it, start_idx, end_idx)
Pango.pango_attr_iterator_range(attr_it, start_idx, end_idx)
(start_idx[1], end_idx[1])
end

Expand All @@ -312,28 +266,23 @@ function unpack_pango_attr_list(ptr::Ptr{Cvoid})
push!(attrs, (start_idx, attr))
end

ccall((:pango_attr_iterator_destroy, libpango),
Cvoid, (Ptr{Cvoid},), attr_it)
Pango.pango_attr_iterator_destroy(attr_it)

attrs
end


function pango_to_svg(text::AbstractString)
c_stripped_text = Ref{Ptr{UInt8}}()
c_stripped_text = Ref{Cstring}()
c_attr_list = Ref{Ptr{Cvoid}}()

ret = ccall((:pango_parse_markup, libpango),
Int32, (Cstring, Int32, UInt32, Ptr{Ptr{Cvoid}},
Ptr{Ptr{UInt8}}, Ptr{UInt32}, Ptr{Cvoid}),
text, -1, 0, c_attr_list, c_stripped_text,
C_NULL, C_NULL)
ret = Pango.pango_parse_markup(text, -1, 0, c_attr_list, c_stripped_text, C_NULL, C_NULL)

ret == 0 && error("Could not parse pango markup.")

# TODO: do c_stripped_text and c_attr_list need to be freed?

text = unsafe_wrap(Array, c_stripped_text[])
text = unsafe_string(c_stripped_text[])

last_idx = 1
open_tag = false
Expand Down Expand Up @@ -363,16 +312,16 @@ function pango_to_svg(text::AbstractString)

if !(attr.rise === nothing)
bs = -((attr.rise / PANGO_SCALE)pt).value
@printf(io, " dy=\"%s\"", pango_fmt_float(bs))
@printf(io, " dy=\"%s\"", Pango.pango_fmt_float(bs))
baseline_shift = bs
elseif baseline_shift != 0.0
@printf(io, " dy=\"%s\"", pango_fmt_float(-baseline_shift))
@printf(io, " dy=\"%s\"", Pango.pango_fmt_float(-baseline_shift))
baseline_shift = 0.0
end

if !(attr.scale === nothing)
@printf(io, " font-size=\"%s%%\"",
pango_fmt_float(100.0 * attr.scale))
Pango.pango_fmt_float(100.0 * attr.scale))
baseline_shift *= attr.scale
end

Expand Down