How do I overhang quotation marks?

I would like quotation marks at the start of a paragraph/block to overhang into the left margin. I have #set text(overhang: true), but it doesn’t seem to affect quotation marks. I looked at the documentation for show rules, but I couldn’t find a query for something like “the first character of a paragraph” (so I could have an if/else inside the show rule for "“"), and I can’t rely on the x position because some of these blocks are indented. I also tried defining my own function to do it manually:

#let ldquooverhang(body) = {
  context [
    #let width = measure(body).width
    #h(-width) #body
  ]
}

That nudges the quotation mark left, but then there’s an awkward space between it and the following character. I would prefer to do it automatically, but I really only care about a couple larger formatted quotes so doing a manual workaround wouldn’t be the end of the world.

You can test if the first character of a paragraph is a quatation mark with

#show par: it =>{
  if not it.body.has("children") or it.body.children.first().func() != smartquote {
    return it
  }
  // else
}

the trickier part is how to indent the lines not caught by the above to the left. Simply adding set par(first-line-indent: <len>) does not work, see the explanation by @Andrew here: Is it possible to have a function in set rule that are inside a show rule? - #7 by Andrew

Luckily, @bluss in the same thread has a workaround: Is it possible to have a function in set rule that are inside a show rule? - #16 by bluss

which we can adapt:

#show par: it => {
  if not it.body.has("children") or it.body.children.first().func() != smartquote {
    return it
  }
  context {
    let len = measure("\"").width
    let fields = it.fields()
    let fli = fields.remove("first-line-indent")
    let body = fields.remove("body")
    if fli.amount == -len {
      return it
    }
    par(
      ..fields,
      first-line-indent: (
        amount: -len,
        all: true,
      ),
      body
    )
  }
}

That worked, thank you!

Measuring quote in a string is wrong, considering the quotes are being added in markup mode, and therefore being converted to smart quotes, which almost definitely use different Unicode characters, which probably will have different width:

#context measure("\"").width

#context measure["].width

image

Also, it.body.children.first() can potentially error, since sequence can be empty, so .at should be used, like here:

Or separately:

1 Like