1

I have a document where paragraph-level headings are placed in the margins. I’ve implemented this using KOMAscript as in the following MWE:

\documentclass{scrartcl}

\RedeclareSectionCommand[afterskip=0pt]{paragraph} \renewcommand{\sectioncatchphraseformat}[4]{% \ifstr{#1}{paragraph} {% \makebox[0pt][r]{\raisebox{0pt}[\height][0pt]{\parbox[t]{2cm}{% \raggedright\relax#4}% }\hskip.25em\relax}% }{% \hskip #2#3#4% }}

\usepackage{lipsum}

\begin{document}

\lipsum[2]

\paragraph{The name of this paragraph} \lipsum[2]

\paragraph{Antidisestablishmentarianism} \lipsum[2]

\end{document}

Unfortunately, this makes long words collide with the body text:

body text with long paragraph heading ‘antidisestablishmentarianism’ interfering

Since hyphenation looks ugly in this context, I’ve decided to solve this by pushing the paragraph down by a line when it would collide with the header. I’m currently using the following macro for this purpose:

\makeatletter
\newcommand{\overlongname}{%
  \rule[-.5\baselineskip]{0pt}{0pt}%
  \@afterindentfalse%
  \@afterheading}
\makeatother

Issuing \overlongname after any problematic \paragraph{…} header is then sufficient to solve the problem.

However, I dislike the manual nature of this approach. Is there any way to automatically detect whether the section header horizontally overflows the parbox, and move the paragraph start when this occurs?

bradrn
  • 212
  • It seems that you set the margin where the header title is to 2 cm, is that right? If so, I think it might be feasible to obtain the length of the sentence that you input in the paragraph command with these approaches: https://tex.stackexchange.com/questions/18576/get-width-of-a-given-text-as-length then it's a matter of checking if it is larger than the margin width and if so, calling the overlongname command within the paragraph one. This is a suggestion, I am not sure if it will work. – Jes Sep 15 '23 at 14:46
  • @Jes If I understand correctly, that approach doesn’t really help, since it finds the width of the text without line breaking. Thus, both of the paragraph headings in my MWE would be considered greater than the margin width, even though the first one can be broken into lines which unproblematically fit in the margin. – bradrn Sep 15 '23 at 15:38
  • Typographic design must take the whole document into consideration. If some of your paragraphs have very long titles, it makes little sense to try to squeeze them in a narrow margin. Hyphenation might be used also for the first word, but think to your poor readers. – egreg Sep 15 '23 at 22:10
  • @egreg I have indeed tried to take that into account. In my actual document, I’ve adjusted the margins so that almost all of the headings fit comfortably (at least in my opinion). Furthermore, most of the headings are one or two short words — this MWE is highly contrived to show the problem. – bradrn Sep 16 '23 at 00:51

2 Answers2

1

It seems LaTeX never hyphenates, or generates \hbox overfull warnings, for the first word of a sentence. (It does complain about \ifstr.) I figured out how to fix that, but now it hypenates instead.

\documentclass{scrartcl}

\RedeclareSectionCommand[afterskip=0pt]{paragraph} \renewcommand{\sectioncatchphraseformat}[4]{% \Ifstr{#1}{paragraph} {% \makebox[0pt][r]{\raisebox{0pt}[\height][0pt]{\parbox[t]{2cm}% {\raggedright\hspace*{-0.333em} #4}% }\hskip.25em\relax}% }{% \hskip #2#3#4% }}

\usepackage{lipsum} \newsavebox{\test}

\begin{document}

\lipsum[2]

\paragraph{The name of this paragraph} \lipsum[2]

\paragraph{Antidisestablishmentarianism} \lipsum[2]

\end{document}


Here is a code fragment to test the first word of a sentence. It uses the xstring package.

\sbox9{\StrBefore{#4 }{ }}%
\ifdim\wd9>2cm\relax \overlongname\fi
John Kormylo
  • 79,712
  • 3
  • 50
  • 120
  • Unfortunately this doesn’t actually solve my problem… like I said, I specifically wanted to avoid hyphenation. – bradrn Sep 15 '23 at 15:35
  • But now that we know to only worry about the first word, one can parse the sentence to pull off the first word and measure its width. – John Kormylo Sep 15 '23 at 15:40
  • Oh, interesting point! That would indeed solve most cases. (It wouldn’t work if the first word is short and the second word is long enough, but I anticipate that case to be fairly rare.) I’m not sure how this answer handles that case at all, though. – bradrn Sep 15 '23 at 15:44
  • 2
    I developed a test, but I can't find a safe place to put it into \sectioncatchphraseformat (without making everything boldface), nor can I get \overlongname to do anything. – John Kormylo Sep 15 '23 at 16:44
  • Thanks @JohnKormylo! I’ll play around with it and see if I have any success. (I’m also thinking now that it might be possible to loop through each word and call \overlongname if any are too long… it feels rather hackish, but should work in all cases.) – bradrn Sep 16 '23 at 00:53
  • I managed to get this largely working, and submitted it as an answer. There’s still some remaining problems though. – bradrn Sep 16 '23 at 03:22
1

Based on @JohnKormylo’s suggestion, this seems to mostly work:

\newsavebox{\overlongtestbox}
\newcommand{\IfOverlongWord}[4]{%
  \IfStrEq{#2}{}{% empty string, base case, use false arg
    #4%
    }{%
      \StrCut{#2}{ }\firsttestword\resttestword%
      \sbox{\overlongtestbox}{\firsttestword}%
      \ifdim\wd\overlongtestbox>#1\relax% overlong word, use true arg
        #3%
      \else% not overlong, now test next word
        \IfOverlongWord{#1}{\resttestword}{#3}{#4}%
      \fi%
    }}

\RedeclareSectionCommand[afterskip=0pt]{paragraph} \renewcommand{\sectioncatchphraseformat}[4]{% \Ifstr{#1}{paragraph} {% \IfOverlongWord{2cm}{#4}{% \hspace{\dimexpr-2cm-.25em\relax}% #4% \rule[-.5\baselineskip]{0pt}{0pt}% \par% \noindent }{% \makebox[0pt][r]{\raisebox{0pt}[\height][0pt]{\parbox[t]{2cm}{% \raggedright\relax#4}% }\hskip.25em\relax}% }% }{% \hskip #2#3#4% }}

Essentially, \IfOverlongWord tests each word in turn to see if it’s wider than the threshold. This is then used in \sectioncatchphraseformat, which lowers the next paragraph if the condition holds, and creates a box in the margin otherwise.

Unfortunately, this has a severe limitation: it breaks if I load hyperref! Specifically, I get many errors along the lines of:

./latex-marginpar-mwe.tex:51: Undefined control sequence.
<argument> \begingroup \let \HyperRaiseLinkLength 
                                                  \@tempdima \setlength \Hyp...l.51

./latex-marginpar-mwe.tex:51: Undefined control sequence. <argument> \HyperRaiseLinkLength

l.51

./latex-marginpar-mwe.tex:51: Undefined control sequence. <argument> ...r \raise \the \HyperRaiseLinkLength \hbox {\Hy@RestoreSpaceFac...l.51

./latex-marginpar-mwe.tex:51: You can't use `\hbox' after \the. <argument> ...se \the \HyperRaiseLinkLength \hbox {\Hy@RestoreSpaceFactor \h...l.51

./latex-marginpar-mwe.tex:51: Undefined control sequence. \Hy@wrapper@babel ...set@display@protect \edef \x {#2}@onelevel@sanitize \x...l.51

./latex-marginpar-mwe.tex:51: Undefined control sequence. <argument> \x

This would seem to indicate that I’m doing something badly wrong here, so I won’t accept this answer just yet.

bradrn
  • 212