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

typst images are block elements unless there's a box() around them #9104

Closed
cscheid opened this issue Sep 26, 2023 · 12 comments · Fixed by #9149
Closed

typst images are block elements unless there's a box() around them #9104

cscheid opened this issue Sep 26, 2023 · 12 comments · Fixed by #9149
Labels

Comments

@cscheid
Copy link
Contributor

cscheid commented Sep 26, 2023

Explain the problem.

The output generated by the Typst writer uses image() directly, which is a block-level element. Typst's documentation on box explains:

All elements except inline math, text, and boxes are block-level and cannot occur inside of a paragraph.

As a result, we get this:

$ pandoc -t typst
the page menu [![](page-menu-icon.jpg){width=3mm}]{custom-style="Parameter"}. The various commands
^D
the page menu #image("images/page-menu-icon.png", width: 3mm). The
various commands

This paragraph doesn't render as expected:

image

When wrapped around a box() call, this does:

$ cat output.typ
the page menu #box(image("images/page-menu-icon.png", width: 3mm)). The
various commands
$ typst compile output.typ && open output.pdf

We get this:

image

Pandoc version?

$ pandoc --version
pandoc 3.1.8
Features: +server +lua
Scripting engine: Lua 5.4
User data directory: /Users/cscheid/.local/share/pandoc
Copyright (C) 2006-2023 John MacFarlane. Web: https://pandoc.org
This is free software; see the source for copying conditions. There is no
warranty, not even for merchantability or fitness for a particular purpose

How to fix

Unfortunately, it's not a good idea to put a box() around every time, because the box will then be sized as big as possible by default (because of typst's layout algorithm). I've asked the typst devs about a trick for this, and am waiting for their answer. With that said, if users size their images explicitly as above, then it is safe to wrap them in a box() call, since that's almost certainly going to produce the behavior the user expects.

@cscheid cscheid added the bug label Sep 26, 2023
jgm added a commit that referenced this issue Oct 20, 2023
An `#image` by itself in typst is a block-level element.
To force images to be inline (as they are in pandoc), we need
to add a box with an explicit width. When a width is not given
in image attributes, we compute one from the image itself, when
possible.

Closes #9104, supersedes #9105.
@jgm jgm closed this as completed in #9149 Oct 20, 2023
jgm added a commit that referenced this issue Oct 20, 2023
An `#image` by itself in typst is a block-level element.
To force images to be inline (as they are in pandoc), we need
to add a box with an explicit width. When a width is not given
in image attributes, we compute one from the image itself, when
possible.

Closes #9104, supersedes #9105.
@cscheid
Copy link
Contributor Author

cscheid commented Nov 9, 2023

I'm sorry, I think we need to reopen this. I only now realize the fix isn't good in the presence of relative sizes.

Consider this:

$ pandoc --to typst --from markdown
![](elephant.png){width="20%"}
^D
#box(width: 20%, image("elephant.png", width: 20%))

It'll make the image 20%^2 = 4% of the width...

@jgm jgm reopened this Nov 9, 2023
@jgm
Copy link
Owner

jgm commented Nov 9, 2023

OK, so what's the fix? Is it just a simple matter of making the inner width always 100%?

@cscheid
Copy link
Contributor Author

cscheid commented Nov 9, 2023

Either making the inner width 100% (and forwarding the attributes to the box) or choosing either the image or the box to take the sizing appears to work.

Here's what I've observed, with no theory or deep knowledge of typst 0.9.0's layout algorithm:

= Good

We want this to be inline: #box[#image(width: 20%, "elephant.png")] Etc.

We want this to be inline: #box(width: 20%, [#image("elephant.png")]) Etc.

We want this to be inline: #box(width: 150pt, [#image("elephant.png")]) Etc.

We want this to be inline: #box([#image(width: 150pt, "elephant.png")]) Etc.

#pagebreak()


= Not good

General observation: sizing the box _and_ the image simultaneously appears to displease the layout engine:

We want this to be inline: #box(width: 100pt, [#image(width: 200pt, "elephant.png")]) Etc.


We want this to be inline: #box(width: 20%, [#image(width: 150pt, "elephant.png")]) Etc.

We want this to be inline: #box(width: 20%, [#image(width: 20%, "elephant.png")]) Etc.

#pagebreak()

= But 100% in the image width appears to be ok

We want this to be inline: #box(width: 150pt, [#image(width: 100%, "elephant.png")]) Etc.

We want this to be inline: #box([#image(width: 100%, "elephant.png")]) Etc.


We want this to be inline: #box(width: 20%, [#image(width: 100%, "elephant.png")]) Etc.

These render to

image image image

@jgm
Copy link
Owner

jgm commented Nov 9, 2023

Does the "not providing width for the image" approach still work when the box size is bigger than the image's native size?

@cscheid
Copy link
Contributor Author

cscheid commented Nov 9, 2023

Does the "not providing width for the image" approach still work when the box size is bigger than the image's native size?

I don't know what you mean by work, but it expands the image to fit the box:

We want this to be inline: #box(width: 300pt, [#image("tiny.jpg")]) Etc.

produces

image

(tiny.jpg is 66x70 natively)

I'm not sure what the expected behavior should be in this case, but

We want this to be inline: #box(width: 300pt, [#image(width: 66pt, "tiny.jpg")]) Etc.

produces this:

image

I suppose that if the desired behavior is "expand if larger" so that if users specify a large width, they get a big image, then the first approach in this message is the correct one. Empirically, the sizing behavior of images in Typst seems to be "expand to fill the line width or the box width if unsized".

It also occurs to me that one disadvantage of having Pandoc always put some sizing information is that it loses the "pure Typst" behavior even in the case where the Image appears in a paragraph by itself (a "block context", if you will). It seems it might be prudent to have some option to tell Pandoc "please treat this image exactly as typst would", because there's no simple post-processing workaround to revert the behavior once sizing information is put into the image or box elements.

@jgm
Copy link
Owner

jgm commented Nov 9, 2023

Isn't the pure typst behavior making the image take the full page width? If so, then setting width to 100% in a block-level image should have the same result, right?

@cscheid
Copy link
Contributor Author

cscheid commented Nov 9, 2023

I don't think so:

= Inner width 100% appears to be a no-op

== With

#box(image(width: 100%, "elephant.png"))

Almost be inline: #box(image(width: 100%, "elephant.png")) Etc.

== Without

#box(image("elephant.png"))

Almost be inline: #box(image("elephant.png")) Etc.

This is the output:

image

I think that typst takes "100%" to mean "100% of the available space", and not "100% of the image size".

@cscheid
Copy link
Contributor Author

cscheid commented Nov 9, 2023

I think there's a simple algorithm that works here.

I think Pandoc users would expect inline unstyled images to behave like they do in other Pandoc formats. So if the image is given without width or height attributes, then Pandoc automatically determines sizing information, and adds it, in pts, to a box surrounding the image.

If Pandoc is given width or height attributes, on the other hand, then these attributes are the ones that determine the box attributes. The only important thing here is that these attributes shouldn't be repeated in both box and image to avoid compounding relative sizing.

I was originally advocating for an attribute that restores the pure-typst behavior, but I think that option is easily achieved with a RawInline, so I no longer think that's truly necessary. It's still useful for Pandoc to be able to set width/height on either the box or the image, because that's not something users can do easily (notably from Lua in our case). But if the choice is to not provide such information, then the RawInline element works fine.

@jgm
Copy link
Owner

jgm commented Nov 10, 2023

My question was whether the appearance in typst is ever different between

#box(image(width: 100%, "elephant.png"))

and

#image("elephant.png")

(as independent blocks). If not, then we can get the behavior of a "pure typst" plain image in typst by just adding the width attribute. No need to result to raw.

Anyway, I think we're in agreement about the algorithm: basically, do what we now do, but don't duplicate the width/height attributes; instead, just add them to the box and leave them off the image.

@iandol
Copy link
Contributor

iandol commented Dec 4, 2023

I'm sorry I am quite confused as to the current status of this issue as it may pertain to standalone images. Currently in Pandoc we have this:

# Title

Text.

![Caption](xkcd.png)
= Title
<title>
Text.

#figure([#box(width: 1480.0pt, image("xkcd.png"))],
  caption: [
    Caption
  ]
)

The width: 1480pt totally breaks layout in Typst PDFs, and you can't use a Lua filter as it isn't in the AST but is generated by the writer?

@jgm jgm closed this as completed in dbdb4d9 Dec 4, 2023
@jgm
Copy link
Owner

jgm commented Dec 4, 2023

I have fixed this issue along the lines agreed to above. More testing is welcome, of course.

@iandol maybe you should introduce a separate issue. In your case is the problem with 1480pt that it is wider than the page width?

We are trying here to make the typst writer behave like other pandoc writers behave. Other writers don't automatically resize images to page width. Instead, they leave the image at its native size, unless a size attribute is specified.

Note that the LaTeX writer has some special code in the preamble that prevents images from overflowing the page; it would be nice to have something like that here, but I'm not sure how to achieve it. In any case, this should really be added as a separate issue.

@iandol
Copy link
Contributor

iandol commented Dec 5, 2023

Great, thanks for the clarifications @jgm. Yes, at the moment, the width=xpt is causing standalone figures to overflow the page margins. I will ask on the Typst forums if there is a way to add a something to stop overflow. And open a new issue for this.

Edit: see #9236

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants