References to floating figures point to wrong location
Description
The location of a floating figure is set to where it is created, but not where it is actually laid out.
This is visible in the location function, and also noticeable when clicking on a reference only to not end up at the figure.
#set page(height: 2.2cm, width: 8cm)
@figure
#figure(
[#rect()<actual>],
placement: top,
caption: [A rectangle]
) <figure>
`figure:` #context query(<figure>).first().location().position() \
`rect: ` #context query(<actual>).first().location().position()
Tried with 0.11.1 on the webapp, and on main with f91cad7d
Reproduction URL
No response
Operating system
Web app, Windows
Typst version
- [X] I am using the latest version of Typst
Closely related to https://github.com/typst/typst/issues/4322#issuecomment-2147712634
This is pretty hard to fix because the location must be in-flow for counters to resolve correctly.
I encountered the some problem on 0.13.0
I encountered the some problem on 0.13.1 😢
I've found a workaround, by using place(top: float: true, figure(...)), the location would be ok for the case in the issue(not sure for other cases). Not working with a show-set and I have no idea why.
I've found a workaround, by using
place(top: float: true, figure(...)), the location would be ok for the case in the issue(not sure for other cases). Not working with a show-set and I have no idea why.
This method works well for me. 👍 Here is a complete example:
#place(bottom, scope: "parent", float: true, [
#figure(
// ...
)<fig:name>
])
A possible workaround is to create a show rule on ref so that it links to the figure's body instead of the figure itself. It's a bit convoluted, as it then also requires a show rule on floating figures to make getting the body's location even possible, but it seems to work.
(Note that this creates a nested figure, so any show rules using relative sizes such as show figure: set text(size: 0.8em) are applied twice, which in this case leads to a text size of 0.64em)
The show rules
#show figure: it => {
if it.placement == none { return it }
// Re-wrap placed figures to include metadata containing
// the body's location.
place(it.placement, float: true, scope: it.scope, block(width: 100%, {
let fields = it.fields()
let body = fields.remove("body")
let label = fields.remove("label")
let counter = fields.remove("counter")
// Need to step back to keep the same number in the new figure.
counter.update(n => n - 1)
let meta = context metadata((
figure-location: it.location(),
body-location: here()
))
figure(meta + body, ..fields, placement: none)
})
}
#show ref: it => {
let fig = it.element
if fig == none { return it }
if fig.func() != figure { return it }
if fig.numbering == none { return it }
if fig.placement == none { return it }
// Rebuild reference from scratch.
let num = numbering(fig.numbering, ..fig.counter.at(fig.location()))
let supplement = (if it.supplement == auto { fig } else { it }).supplement
if supplement not in (text(""), [], none) { supplement += [~] }
// Use location of figure's body for linking.
let location = query(metadata).find(data => (
type(data.value) == dictionary
and data.value.at("figure-location", default: none) == fig.location()
)).value.body-location
link(location, [#supplement#num])
}
Still getting this issue using the following code, where the aqua figure is placed on page 1 and the orange figure on page 2 in all cases:
#set page(numbering: "1")
#outline(title: "List of figures", target: figure.where(kind: image))
#figure(
placement: auto,
box(fill: aqua, width: 90%, height: 50%),
caption: [This is an aqua figure.]
)
#figure(
placement: auto,
box(fill: orange, width: 90%, height: 50%),
caption: [This is an orange figure.]
)
| Typst version | Figure 1 claimed page number | Figure 2 claimed page number |
|---|---|---|
| 0.10.0 | 1 | 1 |
| 0.11.1 | 1 | 1 |
| 0.12.0 | 2 | 2 |
| 0.13.0 | 2 | 2 |
| 0.13.1 | 2 | 2 |
I used the following fix inspired by sjfhsjfh's comment above to successfully correct the outlined page numbers of my 102 (😢) figures in my thesis:
#place(auto, scope: "parent", float: true, [
// figure goes here
])
I can confirm that subpar environments have the same issue as figures and are solved in the same way. Also, I get better placement by using only the place command above and removing the "placement: auto" from the figure or subpar environment.
Faced the same issue in v0.13.1, the place workaround worked for me!
Facing the same issue in v0.14.0
The show rules
The workaround does the job, but the code is broken without ).
Fixed and formatted
#show figure: it => {
if it.placement == none { return it }
// Re-wrap placed figures to include metadata containing
// the body's location.
place(it.placement, float: true, scope: it.scope, block(width: 100%, {
let fields = it.fields()
let body = fields.remove("body")
let label = fields.remove("label")
let counter = fields.remove("counter")
// Need to step back to keep the same number in the new figure.
counter.update(n => n - 1)
let meta = context metadata((
figure-location: it.location(),
body-location: here(),
))
figure(meta + body, ..fields, placement: none)
}))
}
#show ref: it => {
let fig = it.element
if fig == none { return it }
if fig.func() != figure { return it }
if fig.numbering == none { return it }
if fig.placement == none { return it }
// Rebuild reference from scratch.
let num = numbering(fig.numbering, ..fig.counter.at(fig.location()))
let supplement = (if it.supplement == auto { fig } else { it }).supplement
if supplement not in (text(""), [], none) { supplement += [~] }
// Use location of figure's body for linking.
let location = query(metadata)
.find(data => (
type(data.value) == dictionary
and data.value.at("figure-location", default: none) == fig.location()
))
.value
.body-location
link(location, [#supplement#num])
}