typst / typst

A new markup-based typesetting system that is powerful and easy to learn.
https://typst.app
Apache License 2.0
32.08k stars 859 forks source link

Proposal: change `leading` option to `line-height` #4224

Open peng1999 opened 2 months ago

peng1999 commented 2 months ago

I propose deprecating the par.leading option, and add a par.line-height option instead.

Motivation

The current leading model do not persist baseline distance

Current leading model specifies the space between bottom edge of last line and top edge of the next line. This is fine when every line frame is of the same height but becomes undesirable when some lines become taller. See https://github.com/typst/typst/issues/1028#issuecomment-2029983751 for example. To alleviate the problem, currently Typst hacks into the bounding box of inline formula. However, this workaround is not ideal as it leads to the exact issues described in #1028 and lacks a straightforward solution.

Furthermore, line spacing becomes uneven when text with different size appeared in paragraph. No proper solution exists under current leading model.

Typst:

image

LaTeX:

image

The current leading model is confusing and inconsistent with precedence

Typst define leading as “The spacing between lines”. However, other typesetting software such as Adobe InDesign and Affinity Publisher use the term “leading” to refer to the distance between text baselines. Other typesetting systems like CSS, LaTeX, and Microsoft Word do not employ the term “leading”, yet they all relate line spacing to the distance between baselines.

Typography guidelines (example) consider line spacing in terms of baseline distance too.

Proposal

Deprecate the leading option and introduce a new option called line-height, which represents the basic distance between baselines. leading can be removed in the future version along with locate function.

For each line frame $L_i$, define $H_i$ as the distance from the top edge to the baseline (height), and $D_i$ as the distance from the baseline to the bottom edge (depth). During layout processes, Typst should insert a blank frame of height $x$ between two lines, $L_1$ and $L_2$. The height of this frame is determined by: $$x = \max(0, s - D_1 - H_2)$$ where $s$ represents the line-height value. Essentially, we maintain consistent baseline spacing unless there is an collision.

In the future, an additional option min-line-gap$=g$ can be added, changing the above formula to $$x = \max(g, s - D_1 - H_2).$$ See below for the explanation of $g$.

Alternative

The TeX model

(Reference: The TeXBook, p.77-80)

TeX employs the following formula:

$$x = \begin{cases}s - D_1 - H_2, &s - D_1 - H_2 \geq l\\g, &\text{otherwise}\end{cases}$$

where $x$ is the space inserted between lines, and $s$, $l$, $g$ corresponds to \baselineskip, \lineskiplimit and \lineskip respectively. Notably the first formula presented in Proposal section is a special case where $l = g = 0$. So the proposed formula is compatible to the LaTeX algorithm.

https://github.com/typst/typst/issues/1028#issuecomment-2124864812 demonstrated an instance of the model in Typst where $s=14\text{pt}, l=0,$ and $g=1\text{pt}$.

The InDesign model

From InDesign's document:

The default auto-leading option sets the leading at 120% of the type size. […] By default, leading is a character attribute, which means that you can apply more than one leading value within the same paragraph. The largest leading value in a line of type determines the leading for that line.

This model is not suitable for Typst because it do not play well with inline formulas.

Enivex commented 2 months ago

No proper solution exists under current leading model.

Allow different elements on the line to have different values of leading, with the largest "element height + leading" determining the line height. Inline equations could have zero or minimal leading.

That being said, I'm not convinced that the current model is better either. Just saying that there are (at least partial) solutions within the current framework.

Enter-tainer commented 2 months ago

with the largest "element height + leading" determining the line height.

i think it is how current model works? the problem is that, once the "largest element" becomes too large, you no longer want to use the original leading(which is designed for not-so-large cases), but a much smaller value, like 1pt. And that is why inline math are pretending if they are actually smaller.

Enivex commented 2 months ago

with the largest "element height + leading" determining the line height.

i think it is how current model works? the problem is that, once the "largest element" becomes too large, you no longer want to use the original leading(which is designed for not-so-large cases), but a much smaller value, like 1pt. And that is why inline math are pretending if they are actually smaller.

Why couldn't it just always be 1 pt for inline math then? (Disregarding lines solely composed of inline math)

Enter-tainer commented 2 months ago

Why couldn't it just always be 1 pt for inline math then?

i havent think in detail and im not sure if it is a good idea. i personally perfer @peng1999 's because the idea behind is backed by *TeX and is battle tested.

One thing to notice is that "changing the spacing model" sounds difficult but actually doesnt need change a lot of code, at least for the demo in https://github.com/typst/typst/issues/1028#issuecomment-2124864812, the changeset is very small and straight forward. I've also search all occurance of "leading" and found that there isnt too much usage.

Enter-tainer commented 2 months ago

Sorry i didn't read this yesterday.

Allow different elements on the line to have different values of leading

This sounds even more complicated than any model mentioned here. IIUC spacing models mentioned here(apart from in design) all have a single leading/line-height within a paragraph. And the one you mentioned, can set different leading for every element.

But i'm not sure what it will looks like in typst. Currently leading is only defined in par. Should we add leading to all other elements like text, equation(and set leading automatically depending on if it's inline/block)? I feel like it is not a good user interface.

Another thing to notice is that leading in InDesign actually means line-height

laurmaedje commented 2 months ago

Will leave a comment here once I've had the chance to discuss this with @reknih. I'll try to recover the original reasoning for the current model.

Andrew15-5 commented 2 months ago

Related: #159, #1221.

The (good-enough) solution I currently use everywhere: https://github.com/typst/typst/issues/159#issuecomment-1609939896.

laurmaedje commented 2 months ago

The current model

Let me first lay out our original thoughts behind the thoughts behind our original design:

The proposed model

laurmaedje commented 2 months ago

This is only slightly related, but still somewhat relevant: https://github.com/typst/typst/pull/4390

Enter-tainer commented 2 months ago

When implementing this in #4236. I found it hard to deal with spacing between paragraphs. Especially for a tight list/enum/term.

Paragraph spacing difference

This leads to another difference in typst and LaTeX. In typst, the spacing between paragraphs is defined as the distance between the bottom edge of previous paragraph and the top edge of current paragraph. If you set it to 0, these 2 paragraph will be very close. (Actually, they are too close, look at the h and g. Maybe this is bacause the font metrics.)

typst

However, in LaTeX, it layouts text line by line, and each line always has a minimal spacing with the first line. If we set \parskip to zero, it will make the distance between paragraphs looks like the distance between lines within a paragraph.

\usepackage{parskip}
\setlength{\parskip}{0pt}

image

Tight list implementation

Back to the tight list implementation issue. In LaTeX, each list item is a paragraph. To make a list look tight, simply remove all extra inserted spaces, like setting \parskip, \itemskip to 0.

In typst, the implemetation is similar (at least for term list). The distance between paragraphs should be carefully calculated. In the previous model, it should equals to leading: the distance between lines' top/bottom edge.

However this make it very hard when it comes to the line height model. In this way, we don't know "the distance between lines' top edge and bottom edge" unless we can see the ascent and descent of lines. And currently I cannot get it when it cross the boundary of paragraphs.

I don't know if there is a way to robustly get "the descent of the last line in previous par" and "the ascent of the first line in current par". Or how hard it would be to implement this mechenism? If this is possible, we can do this and get a nice tight list. Otherwise we might need to consider how to deal with paragraph spacing in this proposal cc @peng1999

laurmaedje commented 1 month ago

@peng1999 do you have any thoughts on my response?

peng1999 commented 1 month ago

@laurmaedje These are some of my thoughts.


  • That the line height doesn't react to font change is both a benefit and a downside at the same time, in my opinion.

Actually it can react to font change. Just set the line-height to em's.


  • Moreover, the question is how paragraph spacing interacts with this.

The core idea of the proposal is to consider baseline when calculating spacing. In line spacing, we maintain the equation:

$$\text{space between baselines} = \texttt{par.line-height}$$

In paragraph spacing, we effectively deal with baseline distance between last line of the previous prargraph and the first line of the current:

$$\text{space between baselines} = \texttt{par.line-height} + \texttt{par.spacing}$$

The similar calculation happens at tight list/enum/term. See https://github.com/typst/typst/issues/4224#issuecomment-2171089296 for implementation details.


Reguarding to the spacing related to surrounding containers, I don't think there is anything that need to be changed.

laurmaedje commented 1 month ago

Actually it can react to font change. Just set the line-height to em's.

Then it reacts to font size change, but not to font change. 1em == text.size independently of the font.

In paragraph spacing, we effectively deal with baseline distance between last line of the previous prargraph and the first line of the current ...

That's definitely possible, but would definitely require further changes as paragraphs cannot be considered as isolated frames. Still doable.

Reguarding to the spacing related to surrounding containers, I don't think there is anything that need to be changed.

Then, then equation problem remains for the ascent of the first line and descent of the last line.

peng1999 commented 1 month ago

Then it reacts to font size change, but not to font change. 1em == text.size independently of the font.

Then it's actually a good thing. Because it's intuitive for anyone who used professional typography tools (LaTeX/InDesign/Affinity Publisher) before. The current model is quite unfamiliar, especially for those accustomed with the concept of text baseline.

Then, then equation problem remains for the ascent of the first line and descent of the last line.

It depend on your definition of the equation problem. In my opinion, the problem is "Inline formula causes uneven line spacing in text flow". This problem does not affect the first line and last line, because there is no such thing as "line spacing" before the first line and after the last line.

The first and last line is related to "text inside a container" problem you mentioned, but I think the problem is orthogonal.

That's definitely possible, but would definitely require further changes as paragraphs cannot be considered as isolated frames. Still doable.

I'm aware of the complexity of the spacing model change, and @Enter-tainer's hard work on #4236 clearly demonstrates this. Maybe @Enivex's solution (https://github.com/typst/typst/issues/4224#issuecomment-2125126270) is easier to implement.

laurmaedje commented 1 month ago

The first and last line is related to "text inside a container" problem you mentioned, but I think the problem is orthogonal.

That's fair to an extent (both solutions don't fix it), but I think the various problems with equations still need to be considered holistically.

Enter-tainer commented 4 weeks ago

@laurmaedje Could you elaborate more about the "equation problem"? (And maybe especially about the first line and last line) I hope we are on the same page. I suppose you mean that current line height PR only fixed #1028 within a paragraph. But the distance between paragraph will become too much(or is it? I think it should still be par spacing?) in that PR if inline equation appears in the first/last line of a paragraph.

laurmaedje commented 3 weeks ago

What I mean with "equation problem" is that, if we get rid of the slack / reduced height of equations, add line-height, but keep top-edge and bottom-edge as they are now, then the first and last lines will fully expand much more due to equations than they do currently.

For instance, in a two-column layout, where an equation happens to be in the first line of the second column, the whole second column will have slightly shifted lines. In absence of grid-based typesetting, the same can of course always happen due to a quite large equation, but I think with the current proposal, it would happen quite quickly since many equations have a top edge exceeding the text top edge.

Enter-tainer commented 3 weeks ago

if we get rid of the slack / reduced height of equations, add line-height, but keep top-edge and bottom-edge as they are now, then the first and last lines will fully expand much more due to equations than they do currently.

For me I think it is like we need to employ similar method in this proposal. Maybe, instead of using "distance between bbox" to measure paragraph spacing, we need use something like \parskip and do similar max(...) calculations. But I'm really not sure about this at this moment. I need checkout how LaTeX handles this.

it would happen quite quickly since many equations have a top edge exceeding the text top edge.

BTW, what's expected behavior? I'm afraid it will never look like previous slackness hack since it manually fake the bbox of equations so it is... not correct. For me, the ideal result is that the distance between paragraphs will get bigger but shouldnt be too much.

laurmaedje commented 3 weeks ago

BTW, what's expected behavior?

I'm honestly not sure.

Maybe, instead of using "distance between bbox" to measure paragraph spacing, we need use something like \parskip and do similar max(...) calculations.

Perhaps. I am not just wondering about the spacing between paragraphs though, but also about the one from the top of the page / container to the first baseline. And similarly from the last baseline to the bottom of a container.