// Part of the slide_preview library — see ../slide_preview.dart. // Split out for navigability; all imports live in the main library file. part of '../slide_preview.dart'; /// Een 'broncode-sheet': de code op een donker editor-vlak, met /// syntaxkleuring wanneer een taal bekend is. De tekst blijft platte tekst maar /// wordt monospace en gekleurd weergegeven. Past zich met een FittedBox aan de /// slide aan zodat lange fragmenten netjes verkleinen i.p.v. af te kappen. class _CodePreview extends StatelessWidget { final Slide slide; final double w; final String font; final ThemeProfile profile; const _CodePreview({ required this.slide, required this.w, required this.font, required this.profile, }); /// Natural (unwrapped) size of [text] in [style]: width is the longest line, /// height the full block. Used to scale code to the available space. static Size _measureMono(String text, TextStyle style) { final painter = TextPainter( text: TextSpan(text: text.isEmpty ? ' ' : text, style: style), textDirection: TextDirection.ltr, )..layout(); return painter.size; } @override Widget build(BuildContext context) { _ensureHighlightLanguages(); final pad = w * 0.05; final safe = slide.showLogo ? _logoSafeInsets(w, profile) : EdgeInsets.zero; final code = slide.customMarkdown; final lang = slide.codeLanguage.trim(); final known = lang.isNotEmpty && allLanguages.containsKey(lang); final codeBg = _hexColor(profile.codeBackgroundColor); final codeFg = _hexColor(profile.codeTextColor); // The chosen monospace family, always backed by a generic monospace fallback // so an uninstalled face still renders fixed-width. final fallback = ['Menlo', 'Consolas', 'Courier New', 'monospace'] ..removeWhere((f) => f == profile.codeFontFamily); final baseFont = w * 0.024; final maxFont = w * 0.040; // grow to fill, but never huge TextStyle monoAt(double size) => TextStyle( fontFamily: profile.codeFontFamily, fontFamilyFallback: fallback, fontSize: size, height: 1.4, color: codeFg, ); // HighlightView throws on an unknown language, so fall back to plain (but // monospace) text. When syntax highlighting is off we always render plain // text so the whole block is one colour — needed for a CRT-green screen. final useHighlight = known && profile.codeHighlightSyntax; final highlightTheme = { ...atomOneDarkTheme, // Keep atom-one-dark's per-token colours but drop its own background so // our themed [codeBg] shows through unchanged. 'root': (atomOneDarkTheme['root'] ?? const TextStyle()).copyWith( backgroundColor: codeBg, color: codeFg, ), }; Widget buildCode(TextStyle style) => useHighlight ? HighlightView( code, language: lang, theme: highlightTheme, padding: EdgeInsets.zero, textStyle: style, ) : Text(code, style: style); return Container( color: _hexColor(profile.slideBackgroundColor), child: Padding( padding: EdgeInsets.fromLTRB( pad, pad + safe.top, pad, pad + safe.bottom, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // The slide title belongs to the slide, not inside the code window, // so it sits above the panel like other slide types. if (slide.title.isNotEmpty) ...[ Container( width: double.infinity, padding: EdgeInsets.symmetric( horizontal: w * 0.025, vertical: w * 0.01, ), decoration: BoxDecoration( color: _hexColor(profile.titleBackgroundColor), borderRadius: BorderRadius.circular(w * 0.012), border: Border( left: BorderSide( color: _hexColor(profile.accentColor), width: w * 0.006, ), ), ), child: _md( context, slide.title, _applyFont( font, TextStyle( fontSize: w * 0.032, height: 1.1, fontWeight: FontWeight.bold, color: _hexColor(profile.titleTextColor), ), ), linkColor: _hexColor(profile.accentColor), ), ), SizedBox(height: w * 0.018), ], Expanded( child: Container( width: double.infinity, decoration: BoxDecoration( color: codeBg, borderRadius: BorderRadius.circular(w * 0.012), border: Border.all(color: codeFg.withValues(alpha: 0.22)), ), padding: EdgeInsets.all(w * 0.03), child: LayoutBuilder( builder: (context, constraints) { // Size the code to fill the panel: scale up to use spare // space (capped at [maxFont]) and down so long fragments // still fit, rather than leaving a small block in a big box. final measured = useHighlight ? code.replaceAll('\t', ' ') : code; final natural = _measureMono(measured, monoAt(baseFont)); final availW = math.max(1.0, constraints.maxWidth - 1); final availH = math.max(1.0, constraints.maxHeight - 1); var scale = math.min( availW / natural.width, availH / natural.height, ); if (!scale.isFinite || scale <= 0) scale = 1; final size = math.min(baseFont * scale, maxFont); return Align( alignment: Alignment.topLeft, child: buildCode(monoAt(size)), ); }, ), ), ), ], ), ), ); } } /// Register highlight.js language definitions once, so [HighlightView] can /// colour any common language without throwing. bool _highlightReady = false; void _ensureHighlightLanguages() { if (_highlightReady) return; allLanguages.forEach(highlight.registerLanguage); _highlightReady = true; } // ── Shared helper ─────────────────────────────────────────────────────────────