Skip to content

Commit eec24f1

Browse files
feat: soft breaking in PDF identifiers (#720)
Adds soft-breaking rules for identifiers in PDF mode, so the hboxes can be less overfull.
1 parent 04598b6 commit eec24f1

File tree

4 files changed

+101
-18
lines changed

4 files changed

+101
-18
lines changed

src/tests/integration/inheritance-doc/expected/tex/main.tex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@
143143

144144
\cleardoublepage
145145
\begin{docstringBox}{structure}
146-
\LeanVerb|Verso.Integration.InheritanceDoc.FooExtends : Type|\tcblower Documentation for FooExtends\par\noindent\textbf{Constructor}\par \par \LeanVerb|Verso.Integration.InheritanceDoc.FooExtends.mk|\par\noindent\textbf{Extends}\par Verso.Integration.InheritanceDoc.FooExtends\par\noindent\textbf{Fields}\par \par \LeanVerb|barField1| : \LeanVerb|Bool|\par Inherited from \LeanVerb|BarExtended|\par \LeanVerb|barField2| : \LeanVerb|Unit|\par Inherited from \LeanVerb|BarExtended|\par \LeanVerb|fooField1| : \LeanVerb|Nat|\par Documentation for fooField1\par \LeanVerb|fooField2| : \LeanVerb|String|\par Documentation for fooField2
146+
\LeanVerb|Verso.\allowbreak{}Integration.\allowbreak{}Inheritance\-Doc.\allowbreak{}Foo\-Extends : Type|\tcblower Documentation for FooExtends\par\noindent\textbf{Constructor}\par \par \LeanVerb|Verso.\allowbreak{}Integration.\allowbreak{}Inheritance\-Doc.\allowbreak{}Foo\-Extends.\allowbreak{}mk|\par\noindent\textbf{Extends}\par Verso.Integration.InheritanceDoc.FooExtends\par\noindent\textbf{Fields}\par \par \LeanVerb|bar\-Field1| : \LeanVerb|Bool|\par Inherited from \LeanVerb|Bar\-Extended|\par \LeanVerb|bar\-Field2| : \LeanVerb|Unit|\par Inherited from \LeanVerb|Bar\-Extended|\par \LeanVerb|foo\-Field1| : \LeanVerb|Nat|\par Documentation for fooField1\par \LeanVerb|foo\-Field2| : \LeanVerb|String|\par Documentation for fooField2
147147
\end{docstringBox}
148148

149149

src/tests/integration/sample-doc/expected/tex/main.tex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@
143143

144144
\cleardoublepage
145145
\begin{docstringBox}{def}
146-
\LeanVerb|Verso.Integration.SampleDoc.sample_constant : Type|\tcblower This is a docstring.Here's some more text with a \LeanVerb|code inline| in it.
146+
\LeanVerb|Verso.\allowbreak{}Integration.\allowbreak{}Sample\-Doc.\allowbreak{}sample_constant : Type|\tcblower This is a docstring.Here's some more text with a \LeanVerb|code inline| in it.
147147
Here's when a \LeanVerb|code inline|
148148
occurs right before a line break.And then here's a paragraph break.
149149
\end{docstringBox}

src/verso/Verso/Code/HighlightedToTex.lean

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,42 +39,45 @@ public def highlightToken : String → Token.Kind → TeX
3939
| c, .withType _ => .raw c
4040
| c, .unknown => .raw c
4141

42-
defmethod Highlighting.Token.toVerbatimTeX (t : Highlighting.Token) : Verso.Output.TeX :=
43-
highlightToken (escapeForVerbatim t.content) t.kind
42+
defmethod Highlighting.Token.toVerbatimTeX (t : Highlighting.Token) (lineBreaks : Bool := false) : Verso.Output.TeX :=
43+
highlightToken (escapeForVerbatim t.content lineBreaks) t.kind
4444

4545
open Verso.Output.TeX in
4646
/--
4747
Returns TeX that is appropriate for the content of a `\Verb` environment (from package `fancyvrb`)
4848
with command characters `\`, `{`, and `}`.
49+
50+
When `lineBreaks` is true, inserts line break opportunities in identifiers.
4951
-/
50-
public defmethod Highlighted.toVerbatimTeX : Highlighted → Verso.Output.TeX
51-
| .token t => highlightToken (escapeForVerbatim t.content) t.kind
52-
| .text str => .raw (escapeForVerbatim str)
53-
| .seq hts => .seq <| hts.map (·.toVerbatimTeX)
52+
public defmethod Highlighted.toVerbatimTeX (h : Highlighted) (lineBreaks : Bool := false) : Verso.Output.TeX :=
53+
match h with
54+
| .token t => highlightToken (escapeForVerbatim t.content lineBreaks) t.kind
55+
| .text str => .raw (escapeForVerbatim str lineBreaks)
56+
| .seq hts => .seq <| hts.map (·.toVerbatimTeX lineBreaks)
5457
| .span info content =>
5558
if h : info.size > 0
56-
then .seq #[.raw s!"\\{info[0].1.toString}Decorate\{", content.toVerbatimTeX, .raw "}"]
57-
else content.toVerbatimTeX
58-
| .tactics _info _start _end content => content.toVerbatimTeX
59+
then .seq #[.raw s!"\\{info[0].1.toString}Decorate\{", content.toVerbatimTeX lineBreaks, .raw "}"]
60+
else content.toVerbatimTeX lineBreaks
61+
| .tactics _info _start _end content => content.toVerbatimTeX lineBreaks
5962
| .point _kind _info => \TeX{"[Point]"}
60-
| .unparsed str => .raw (escapeForVerbatim str)
63+
| .unparsed str => .raw (escapeForVerbatim str lineBreaks)
6164

6265
def verbatimBlock (t : Verso.Output.TeX) : Verso.Output.TeX :=
6366
.seq #[.raw "\\begin{LeanVerbatim}\n", t, .raw "\n\\end{LeanVerbatim}\n"]
6467

6568
public defmethod Highlighting.Token.toTeX [Monad m] [GenreTeX g m] (t : Highlighting.Token) :
6669
TeXT g m Verso.Output.TeX :=
67-
verbatimInline (t.toVerbatimTeX)
70+
verbatimInline (t.toVerbatimTeX (lineBreaks := true))
6871

6972
public defmethod Highlighted.toTeX [Monad m] [GenreTeX g m] (t : Highlighted) :
7073
TeXT g m Verso.Output.TeX :=
7174
let strip := t.trimOneTrailingNl.trimOneLeadingNl
7275
if strip.isEmpty then
7376
pure .empty
7477
else if strip.containsNewline then
75-
pure <| verbatimBlock (strip.toVerbatimTeX)
78+
pure <| verbatimBlock (strip.toVerbatimTeX (lineBreaks := false))
7679
else
77-
verbatimInline (strip.toVerbatimTeX)
80+
verbatimInline (strip.toVerbatimTeX (lineBreaks := true))
7881

7982

8083
open Verso.Output.TeX in

src/verso/Verso/Doc/TeX.lean

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,103 @@ def replaceChars (s : String) (replace : Char → Option String) : String :=
105105
loop "" 0
106106

107107
/--
108-
Escapes a string in an appropriate way for uses of `\Verb` and
108+
Escapes a single character in an appropriate way for uses of `\Verb` and
109109
`\begin{Verbatim}...\end{Verbatim}` (from package `fancyvrb`) with
110110
command characters `\`, `{`, and `}`.
111111
-/
112-
public def escapeForVerbatim (s : String) : String :=
113-
replaceChars s fun
112+
private def escapeCharForVerbatim (c : Char) : Option String :=
113+
match c with
114114
| '{' => some "\\symbol{123}"
115115
| '|' => some "\\symbol{124}"
116116
| '}' => some "\\symbol{125}"
117117
| '\\' => some "\\symbol{92}"
118118
| '#' => some "\\symbol{35}" -- FIXME: this is really just a test
119119
| _ => none
120120

121+
/--
122+
Tracks the previous character class for inserting line break opportunities.
123+
-/
124+
private inductive PrevChar
125+
/-- previous was lowercase letter -/
126+
| lower
127+
/-- previous was digit -/
128+
| digit
129+
/-- previous was '.' -/
130+
| dot
131+
/-- initial / anything else -/
132+
| other
133+
134+
/--
135+
Escapes a string in an appropriate way for uses of `\Verb` and
136+
`\begin{Verbatim}...\end{Verbatim}` (from package `fancyvrb`) with
137+
command characters `\`, `{`, and `}`.
138+
139+
When `lineBreaks` is true, inserts line break opportunities:
140+
- After `.`: insert `\allowbreak{}` (no hyphen on break)
141+
- On lowercase→uppercase transition: insert `\-` (soft hyphen)
142+
- On digit→letter transition: insert `\-` (soft hyphen)
143+
-/
144+
public def escapeForVerbatim (s : String) (lineBreaks : Bool := false) : String :=
145+
if !lineBreaks then
146+
replaceChars s escapeCharForVerbatim
147+
else Id.run do
148+
let mut state : PrevChar := .other
149+
let mut result : String := ""
150+
for c in s.toList do
151+
-- Emit break if transition warrants it
152+
let break_ := match state with
153+
| .dot => if c != '.' then "\\allowbreak{}" else ""
154+
| .lower => if c.isUpper then "\\-" else ""
155+
| .digit => if c.isAlpha then "\\-" else ""
156+
| .other => ""
157+
-- Emit escaped char
158+
let escaped := escapeCharForVerbatim c |>.getD c.toString
159+
-- Update state
160+
state :=
161+
if c.isLower then .lower
162+
else if c.isDigit then .digit
163+
else if c == '.' then .dot
164+
else .other
165+
result := result ++ break_ ++ escaped
166+
return result
167+
121168
/-- info: "\\symbol{123}\\symbol{124}\\symbol{125}\\symbol{92}" -/
122169
#guard_msgs in
123170
#eval escapeForVerbatim "{|}\\"
124171

172+
-- Tests for lineBreaks functionality
173+
/-- info: "Nat.\\allowbreak{}add\\-One" -/
174+
#guard_msgs in
175+
#eval escapeForVerbatim "Nat.addOne" (lineBreaks := true)
176+
177+
/-- info: "List.\\allowbreak{}map2\\-Fun" -/
178+
#guard_msgs in
179+
#eval escapeForVerbatim "List.map2Fun" (lineBreaks := true)
180+
181+
/-- info: "x2\\-y" -/
182+
#guard_msgs in
183+
#eval escapeForVerbatim "x2y" (lineBreaks := true)
184+
185+
/-- info: "Foo123" -/
186+
#guard_msgs in
187+
#eval escapeForVerbatim "Foo123" (lineBreaks := true) -- no break before digits
188+
189+
/-- info: "a..\\allowbreak{}b" -/
190+
#guard_msgs in
191+
#eval escapeForVerbatim "a..b" (lineBreaks := true) -- only one break after dot sequence
192+
193+
/-- info: "\\symbol{123}foo\\-Bar" -/
194+
#guard_msgs in
195+
#eval escapeForVerbatim "{fooBar" (lineBreaks := true) -- escaping + line breaks
196+
197+
/-- info: "plain" -/
198+
#guard_msgs in
199+
#eval escapeForVerbatim "plain" (lineBreaks := true) -- no transitions
200+
201+
/-- info: "Nat.addOne" -/
202+
#guard_msgs in
203+
#eval escapeForVerbatim "Nat.addOne" -- lineBreaks := false (default), no breaks
204+
125205
/--
126206
Wraps some TeX (which is already assumed to be appropriately escaped in the appropriate
127207
verbatim-like environment depending on whether we're in a fragile environment.

0 commit comments

Comments
 (0)