Repository: typst/typst
File: crates/typst-layout/src/inline/shaping.rs (Rust)

Code before:
                stretchability: (
                    Em::zero(),
                    (width / 2.0) * justification_limits.word_max.rel.get()
                        + Em::from_length(justification_limits.word_max.abs, font_size),
                ),
                shrinkability: (
                    Em::zero(),
                    (width / 3.0) * justification_limits.word_min.rel.get()
                        + Em::from_length(justification_limits.word_min.abs, font_size),
                ),
            }
        } else if self.is_cjk_left_aligned_punctuation(style) {
            Adjustability {
                stretchability: (Em::zero(), Em::zero()),
                shrinkability: (Em::zero(), width / 2.0),
            }
        } else if self.is_cjk_right_aligned_punctuation() {
            Adjustability {
                stretchability: (Em::zero(), Em::zero()),
                shrinkability: (width / 2.0, Em::zero()),
            }
        } else if self.is_cjk_center_aligned_punctuation(style) {
            Adjustability {
                stretchability: (Em::zero(), Em::zero()),
                shrinkability: (width / 4.0, width / 4.0),
            }
        } else {
            Adjustability {
                stretchability: (
                    Em::zero(),
                    width * (justification_limits.glyph_max.rel.get() - 1.0)
                        + Em::from_length(justification_limits.glyph_max.abs, font_size),
                ),
                shrinkability: (
                    Em::zero(),
                    width * (1.0 - justification_limits.glyph_min.rel.get())
                        + Em::from_length(justification_limits.glyph_min.abs, font_size),
                ),
            }
        }
    }

    /// The stretchability of the character.
    pub fn stretchability(&self) -> (Em, Em) {
        self.adjustability.stretchability
    }

    /// The shrinkability of the character.
    pub fn shrinkability(&self) -> (Em, Em) {
        self.adjustability.shrinkability
    }

Review comment (suggestion):
It seems odd to me that the calculation works differently for the space. Wouldn't it make more sense for the default value of `justification-limits` to be adjusted instead? Basically making this the default value:
```typ
#(
  word: (
    min: 100% * (2 / 3),
    max: 150%,
  ),
  glyph: (
    min: 100%,
    max: 100%,
  )
)
```
And then we'd remove all of the `none` and `auto` things and just add suggested values for `glyph` to the documentation. 

I'd also (as suggested by someone on Discord) add support for _folding_ such that `#set par(justification-limits: (glyph: (min: 98%, max: 102%))` does not override the existing values for `word` and `#set par(justification-limits: (glyph: (min: 95%))` not the existing value for `glyph.max`. Dealing with folding is always a bit tricky though, so I could handle that myself.

Code after:
        // Do not ever shrink away more than three quarters of the glyph. As the
        // width approaches zero, justification gets increasingly expensive and
        // if negative it may not terminate.
        let limited = |v: Em| v.min(width * 0.75);

        if self.is_space() {
            // To a space, both the spacing and tracking limits apply, just like
            // `text.tracking` and `text.spacing` both apply to spaces.
            let max = limits.spacing().max + limits.tracking().max;
            let min = limits.spacing().min + limits.tracking().min;
            Adjustability {
                stretchability: (
                    Em::zero(),
                    (max - Rel::one())
                        .map(|length| Em::from_length(length, font_size))
                        .relative_to(width)
                        .max(Em::zero()),
                ),
                shrinkability: (
                    Em::zero(),
                    limited(
                        (Rel::one() - min)
                            .map(|length| Em::from_length(length, font_size))
                            .relative_to(width),
                    ),
                ),
            }
        } else if self.is_cjk_left_aligned_punctuation(style) {
            Adjustability {
                stretchability: (Em::zero(), Em::zero()),
                shrinkability: (Em::zero(), width / 2.0),
            }
        } else if self.is_cjk_right_aligned_punctuation() {
            Adjustability {
                stretchability: (Em::zero(), Em::zero()),
                shrinkability: (width / 2.0, Em::zero()),
            }
        } else if self.is_cjk_center_aligned_punctuation(style) {
            Adjustability {
                stretchability: (Em::zero(), Em::zero()),
                shrinkability: (width / 4.0, width / 4.0),
            }
        } else if stretchable {
            Adjustability {
                stretchability: (
                    Em::zero(),
                    Em::from_length(limits.tracking().max, font_size).max(Em::zero()),
                ),
                shrinkability: (
                    Em::zero(),
                    limited(Em::from_length(-limits.tracking().min, font_size)),