@@ -21,214 +21,6 @@ open Verso.Genre.Manual.InlineLean.Scopes (getScopes runWithOpenDecls runWithVar
2121
2222namespace Manual
2323
24- section BlockExtension
25-
26- block_extension Block.diagram (svg : String) (width : String) (inline : Bool) where
27- data := Json.arr #[.str svg, .str width, .bool inline]
28- extraCss := [
29- r#"
30- .diagram svg text[font-family=" text"] {
31- font-family: var(--verso-text-font-family);
32- }
33- .diagram svg text[font-family=" monospace"] {
34- font-family: var(--verso-code-font-family);
35- }
36- " #
37- ]
38- traverse _ _ _ := pure none
39- toHtml :=
40- open Verso.Output.Html in
41- some <| fun _ _ _ data _ => do
42- let .arr #[.str svgStr, .str w, .bool inl] := data
43- | HtmlT.logError "Expected three-element JSON for diagram" *> pure .empty
44- let style := "width: " ++ w ++ if inl then "; display: inline-block; vertical-align: middle" else ""
45- pure {{
46- <div class ="diagram" style={{style}}>
47- {{Html.text false svgStr}}
48- </div>
49- }}
50- usePackages := ["svg" ]
51- toTeX :=
52- some <| fun _ _ id data _ => do
53- let .arr #[.str svgStr, .str w, .bool inl] := data
54- | TeX.logError "Expected three-element JSON for diagram" *> pure .empty
55- let filename := s! "diagram-{ id} .svg"
56- IO.FS.writeFile filename svgStr
57- let widthOpt := if w == "100%" then "width=\\ textwidth" else s! "width={ w} "
58- if inl then
59- pure (.raw s! "\\ includesvg[{ widthOpt} ]\{ {filename} }" )
60- else
61- pure (.raw s! "\\ begin\{ center} \\ includesvg[{ widthOpt} ]\{ {filename} }\\ end\{ center} \n " )
62-
63- block_extension Block.row (alignItems : String) where
64- data := Json.str alignItems
65- traverse _ _ _ := pure none
66- toHtml :=
67- open Verso.Output.Html in
68- some <| fun _ blockHtml _ data content => do
69- let .str ai := data | HtmlT.logError "Expected string JSON for row" *> pure .empty
70- let style := s! "display: flex; flex-wrap: wrap; align-items: { ai} ; gap: 1em;"
71- pure {{
72- <div class ="diagram-row" style={{style}}>
73- {{← content.mapM blockHtml}}
74- </div>
75- }}
76- toTeX :=
77- some <| fun _ go _ _ content => do
78- pure <| .seq <| ← content.mapM go
79-
80- end BlockExtension
81-
82-
83- /--
84- Parses a decimal float string (e.g. `"3.14"`, `"-0.5"`).
85- Returns `none` for unrecognized formats.
86- -/
87- private def parseFloatStr (s : String) : Option Float :=
88- let (neg, s) : Bool × String :=
89- if s.startsWith "-" then (true , (s.drop 1 ).copy) else (false , s)
90- let result : Option Float := match s.splitOn "." with
91- | [intStr] =>
92- intStr.toNat?.map fun n => Float.ofScientific n true 0
93- | [intStr, fracStr] =>
94- intStr.toNat?.bind fun n =>
95- if fracStr.isEmpty then some (Float.ofScientific n true 0 )
96- else fracStr.toNat?.map fun frac =>
97- Float.ofScientific n true 0 + Float.ofScientific frac true fracStr.length
98- | _ => none
99- result.map fun f => if neg then -f else f
100-
101- /--
102- A `ValDesc` for float arguments: accepts integer numerals (e.g. `1`) and
103- string literals containing decimal numbers (e.g. `"0.08"`).
104- -/
105- private def floatValDesc [Monad m] [MonadError m] : ValDesc m Float where
106- description := doc!"a number"
107- signature := .Num ∪ .String
108- get
109- | .num n => pure (Float.ofScientific n.getNat true 0 )
110- | .str s =>
111- match parseFloatStr s.getString with
112- | some f => pure f
113- | none => throwError "Expected a number, got {repr s.getString}"
114- | .name n => throwError "Expected a number, got identifier {n.getId}"
115-
116- section Config
117-
118- variable [Monad m] [MonadInfoTree m] [MonadLiftT CoreM m] [MonadEnv m] [MonadError m]
119-
120- structure DiagramConfig where
121- width : Option String
122- scale : Option Float
123- inline : Bool
124-
125- def DiagramConfig.parse : ArgParse m DiagramConfig :=
126- DiagramConfig.mk <$> .named' `width true <*> .named `scale floatValDesc true <*> .flag `inline false
127-
128- instance : FromArgs DiagramConfig m := ⟨DiagramConfig.parse⟩
129-
130- structure RowConfig where
131- align : String
132-
133- def RowConfig.parse : ArgParse m RowConfig :=
134- RowConfig.mk <$> .namedD' `align "top"
135-
136- instance : FromArgs RowConfig m := ⟨RowConfig.parse⟩
137-
138- end Config
139-
140- open Illuminate in
141- /--
142- Returns the viewBox width (in diagram units) of an SVG diagram, using the same
143- padding as `Illuminate.diagramToSvg` (padding = 5). Used to compute an em-based
144- CSS width when the `scale` parameter is specified. Returns 0 for empty diagrams.
145- -/
146- def diagramViewBoxWidth (d : Diagram SVG) : Float :=
147- match d.getEnvelope with
148- | .empty => 0
149- | .nonempty env => env Vec2.west + env Vec2.east + 10
150-
151- open Lean.Widget Lean.Elab.Term Lean.Meta Illuminate in
152- private meta unsafe def diagramExpanderUnsafe (config : DiagramConfig) (str : StrLit) :
153- DocElabM Term := withoutAsync do
154- if config.width.isSome && config.scale.isSome then
155- throwErrorAt str "Cannot specify both 'width' and 'scale' in a diagram block"
156-
157- let altStr ← parserInputString str
158-
159- match Parser.runParserCategory (← getEnv) `term altStr (← getFileName) with
160- | .error e => throwErrorAt str e
161- | .ok stx =>
162- let (svgStr, diagramWidth) ← runWithOpenDecls <| runWithVariables fun _vars => do
163- let diaTy ← Meta.mkAppM ``Illuminate.Diagram #[.const ``Illuminate.SVG []]
164- let e ← Elab.Term.elabTerm stx (some diaTy)
165- Term.synthesizeSyntheticMVarsNoPostponing
166- let e ← instantiateMVars e
167-
168- -- Don't evaluate if elaboration produced errors
169- if (← Core.getMessageLog).hasErrors then
170- return ("" , 0 )
171-
172- -- Evaluate to get SVG string
173- let svgExpr := mkApp (mkConst ``Illuminate.diagramToSvg) e
174- let svgStr ← evalExpr String (mkConst ``String) svgExpr
175-
176- -- Compute diagram width from envelope if scale is needed
177- let diagramWidth : Float ← if config.scale.isSome then do
178- let widthExpr := mkApp (mkConst ``Manual.diagramViewBoxWidth) e
179- evalExpr Float (mkConst ``Float) widthExpr
180- else
181- pure 0
182-
183- -- Store diagram for widget RPC re-evaluation
184- let env ← getEnv
185- let opts ← getOptions
186- let id ← Illuminate.nextDiagramId.modifyGet fun n => (n, n + 1 )
187- let sd : Illuminate.StoredDiagram := {
188- env, opts, expr := e, gadgets := #[], regions := {}, returnsDwi := false
189- }
190- Illuminate.diagramStore.modify (·.push (id, sd))
191-
192- -- Attach widget with CSS variable defaults for the infoview context
193- let widgetSvg := "<div style=\" --verso-text-font-family: sans-serif; --verso-code-font-family: monospace;\" >" ++
194- "<style>svg text[font-family=\" text\" ] { font-family: var(--verso-text-font-family); } " ++
195- "svg text[font-family=\" monospace\" ] { font-family: var(--verso-code-font-family); }</style>" ++
196- svgStr ++ "</div>"
197- let props : Json := .mkObj [
198- ("exprId" , toJson id),
199- ("initialSvg" , .str widgetSvg),
200- ("parameters" , .arr #[])]
201- savePanelWidgetInfo Illuminate.diagramWidget.javascriptHash.val (pure props) str
202-
203- pure (svgStr, diagramWidth)
204-
205- let cssWidth : String := match config.scale with
206- | none => config.width.getD "100%"
207- | some scale =>
208- if diagramWidth > 0 then s! "{ diagramWidth * scale} em" else "100%"
209-
210- ``(Block.other (Block.diagram $(quote svgStr) $(quote cssWidth) $(quote config.inline)) #[Block.code $(quote str.getString)])
211-
212- open Lean.Widget Lean.Elab.Term Lean.Meta Illuminate in
213- @ [implemented_by diagramExpanderUnsafe]
214- private opaque diagramExpanderImpl (config : DiagramConfig) (str : StrLit) : DocElabM Term
215-
216- @[code_block]
217- def diagram : CodeBlockExpanderOf DiagramConfig
218- | config, str => diagramExpanderImpl config str
219-
220- /-- Arranges the contained blocks in a horizontal row using flexbox. -/
221- @[directive]
222- def row : DirectiveExpanderOf RowConfig
223- | config, stxs => do
224- let alignItems ← match config.align with
225- | "top" => pure "flex-start"
226- | "middle" => pure "center"
227- | "bottom" => pure "flex-end"
228- | other => throwError "Expected 'top', 'middle', or 'bottom' for 'align', got {repr other}"
229- let args ← stxs.mapM elabBlock
230- ``(Block.other (Block.row $(quote alignItems)) #[ $[ $args ],* ])
231-
23224namespace Diagram
23325open Illuminate
23426
0 commit comments