Skip to content

Commit

Permalink
Merge pull request #54 from ASML-Labs/enable_hyperlinks
Browse files Browse the repository at this point in the history
Enable hyperlinks

feature by @xvries
  • Loading branch information
matthijscox-asml authored Jul 8, 2024
2 parents 100de63 + 4daa960 commit cd18ef8
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 6 deletions.
4 changes: 4 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ my_table = Table(df; offset_x=60, offset_y=80, size_x=150, size_y=40)
push!(s4, my_table)
push!(pres, s4)
# and what about a nice link in slide 2 to the table-slide
text = TextBox(; content="Click here to see a nice table", offset_x=100, offset_y=140, size_x=150, size_y=20, hlink = s4)
push!(s2, text)
pres
# output
Expand Down
5 changes: 4 additions & 1 deletion src/AbstractShape.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ end

function _show_string(shape::AbstractShape, compact::Bool)
return "$(typeof(shape))"
end
end

hlink_xml(hlink) = Dict("a:hlinkClick" => Dict("rId" => "rId$(rid(hlink))", "action" => "ppaction://hlinksldjump"))
has_hyperlink(s::AbstractShape) = hasfield(typeof(s), :hlink) && !isnothing(s.hlink)
11 changes: 10 additions & 1 deletion src/Presentation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct Presentation
function Presentation(slides::Vector{Slide}, author::String, title::String)
pres = new(title, author, Slide[], PresentationState())
if isempty(slides)
slides = [Slide(; title=title, layout=TITLE_SLIDE_LAYOUT)]
slides = [Slide(; title=title, layout=TITLE_SLIDE_LAYOUT, slide_nr = 1)]
end
for slide in slides
push!(pres, slide)
Expand All @@ -68,6 +68,7 @@ end

function Base.push!(pres::Presentation, slide::Slide)
slide.rid = new_rid(pres)
slide.slide_nr = length(slides(pres)) + 1
return push!(slides(pres), slide)
end

Expand Down Expand Up @@ -184,4 +185,12 @@ function update_presentation_state!(p::Presentation, template::ZipBufferReader)
sz = PresentationSize(parse(Int, cx), parse(Int, cy))
p._state.size = sz
return nothing
end

# the slide filename (e.g. slide2.xml) can be used in the slide relationships to enable hyperlinks for example
# We need to make sure that slide N is also really written to slideN.xml therefore slide numbers are updated just before writing
function update_slide_nrs!(p::Presentation)
for (index, slide) in enumerate(slides(p))
slide.slide_nr = index
end
end
28 changes: 25 additions & 3 deletions src/Slide.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ mutable struct Slide
shapes::Vector{AbstractShape}
rid::Int
layout::Int
slide_nr::Int
function Slide(
title::String,
shapes::Vector{<:AbstractShape},
rid::Int=0,
layout::Int=DEFAULT_SLIDE_LAYOUT,
slide_nr::Int=0
)
slide = new(title, AbstractShape[], rid, layout)
slide = new(title, AbstractShape[], rid, layout, slide_nr)
for shape in shapes
push!(slide, shape)
end
Expand All @@ -57,16 +59,17 @@ mutable struct Slide
end
shapes(s::Slide) = s.shapes
rid(s::Slide) = s.rid

slide_nr(s::Slide) = s.slide_nr
Slide(shapes::Vector{<:AbstractShape}; kwargs...) = Slide(; shapes=shapes, kwargs...)

function Slide(;
title::String="",
shapes::Vector{<:AbstractShape}=AbstractShape[],
rid::Int=0,
layout::Int=DEFAULT_SLIDE_LAYOUT,
slide_nr::Int=0
)
return Slide(title, shapes, rid, layout)
return Slide(title, shapes, rid, layout, slide_nr)
end

function new_rid(slide::Slide)
Expand All @@ -77,6 +80,8 @@ function new_rid(slide::Slide)
end
end

slide_fname(s::Slide) = "slide$(s.slide_nr).xml"

function Base.push!(slide::Slide, shape::AbstractShape)
if has_rid(shape)
push!(shapes(slide), set_rid(shape, new_rid(slide)))
Expand Down Expand Up @@ -138,6 +143,20 @@ function init_sptree()::AbstractDict
)
end

function type_schema(s::Slide)
return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
end

function relationship_xml(s::Slide)
return Dict(
"Relationship" => [
Dict("Id" => "rId$(rid(s))"),
Dict("Type" => type_schema(s)),
Dict("Target" => slide_fname(s)),
],
)
end

function make_slide_relationships(s::Slide)::AbstractDict
xml_slide_rels = OrderedDict("Relationships" => Dict[])
push!(
Expand All @@ -160,6 +179,9 @@ function make_slide_relationships(s::Slide)::AbstractDict
if has_rid(shape)
push!(xml_slide_rels["Relationships"], relationship_xml(shape))
end
if has_hyperlink(shape)
push!(xml_slide_rels["Relationships"], relationship_xml(shape.hlink))
end
end
return xml_slide_rels
end
11 changes: 10 additions & 1 deletion src/TextBox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ struct TextBox <: AbstractShape
offset_y::Int # EMUs
size_x::Int # EMUs
size_y::Int # EMUs
hlink::Union{Nothing, Any}
function TextBox(
content::AbstractString,
offset_x::Real, # millimeters
offset_y::Real, # millimeters
size_x::Real, # millimeters
size_y::Real, # millimeters
style::Dict=Dict("bold" => false, "italic" => false),
hlink::Union{Nothing, Any} = nothing
)
# input is in mm
return new(
Expand All @@ -78,6 +80,7 @@ struct TextBox <: AbstractShape
Int(round(offset_y * _EMUS_PER_MM)),
Int(round(size_x * _EMUS_PER_MM)),
Int(round(size_y * _EMUS_PER_MM)),
hlink
)
end
end
Expand All @@ -90,6 +93,7 @@ function TextBox(;
size_x::Real=40, # millimeters
size_y::Real=30, # millimeters
style::Dict=Dict("bold" => false, "italic" => false),
hlink::Union{Nothing, Any} = nothing
)
return TextBox(
content,
Expand All @@ -98,6 +102,7 @@ function TextBox(;
size_x,
size_y,
style,
hlink
)
end

Expand Down Expand Up @@ -130,7 +135,11 @@ function text_style_xml(t::TextBody)
end

function make_xml(t::TextBox, id::Int=1)
cNvPr = Dict("p:cNvPr" => [Dict("id" => "$id"), Dict("name" => "TextBox")])
cNvPr = Dict("p:cNvPr" => Dict[Dict("id" => "$id"), Dict("name" => "TextBox")])
if has_hyperlink(t)
push!(cNvPr["p:cNvPr"], hlink_xml(t.hlink))
end

cNvSpPr = Dict("p:cNvSpPr" => Dict("txBox" => "1"))
nvPr = Dict("p:nvPr" => missing)

Expand Down
1 change: 1 addition & 0 deletions src/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ function Base.write(
mktemp(filedir) do temp_path, temp_out
ZipWriter(temp_out; own_io=true) do w
update_presentation_state!(p, template_reader)
update_slide_nrs!(p)
write_relationships!(w, p)
write_presentation!(w, p)
write_slides!(w, p, template_reader)
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ end

include("testConstructors.jl")
include("testTables.jl")
include("testHyperlinks.jl")
include("testSlideXML.jl")
include("testWriting.jl")
44 changes: 44 additions & 0 deletions test/testHyperlinks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

@testset "Test Hyperlinks" begin
pres = Presentation()
s2 = Slide()
s3 = Slide()

box = TextBox(content = "Hyperlinked text", hlink = s3)

@testset "has hyperlink" begin
@test PPTX.has_hyperlink(box)
box_nolink = TextBox(content = "Hyperlinked text")
@test !PPTX.has_hyperlink(box_nolink)
end

@testset "hyperlink xml" begin
xml = PPTX.hlink_xml(box.hlink)
@test xml["a:hlinkClick"]["rId"] == "rId$(PPTX.rid(s3))"
end

push!(s2, box)
push!(pres, s2)
push!(pres, s3)

@testset "hyperlink xml after rid update" begin
# After updating the rId of s2 the hyperlink xml should also be updated
xml = PPTX.hlink_xml(box.hlink)
@test xml["a:hlinkClick"]["rId"] == "rId$(PPTX.rid(s3))"
end

# I malificently swap slides which should not affect hyperlinking
pres.slides[2] = s3
pres.slides[3] = s2

@testset "update slide nrs" begin
PPTX.update_slide_nrs!(pres)
@test pres.slides[2].slide_nr == 2
@test pres.slides[3].slide_nr == 3
xml_rels = PPTX.relationship_xml(box.hlink)
# Chech that rId is still the same
@test xml_rels["Relationship"][1]["Id"] == "rId$(PPTX.rid(s3))"
@test xml_rels["Relationship"][3]["Target"] == "slide$(PPTX.slide_nr(s3)).xml"
end

end

0 comments on commit cd18ef8

Please sign in to comment.