Added support for arbitrary lists and experience in markup
This commit is contained in:
		
							parent
							
								
									976c3d0679
								
							
						
					
					
						commit
						aa71e8a9e6
					
				@ -2,9 +2,11 @@ import 'dart:async';
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:dartboard_resume/dartboard_parser.dart';
 | 
			
		||||
import 'package:dartboard_resume/render.dart';
 | 
			
		||||
import 'package:hotreloader/hotreloader.dart';
 | 
			
		||||
import 'package:logging/logging.dart' as logging;
 | 
			
		||||
import 'package:toml/toml.dart';
 | 
			
		||||
 | 
			
		||||
StreamSubscription<FileSystemEvent>? fileStreamSub;
 | 
			
		||||
StreamSubscription<String>? stdinStreamSub;
 | 
			
		||||
@ -24,6 +26,12 @@ Future<void> main(List<String> arguments) async {
 | 
			
		||||
        stdout.writeln("Triggering pdf render...");
 | 
			
		||||
        renderPdf(tomlFilePath, force: true);
 | 
			
		||||
      }
 | 
			
		||||
      if (event == "p") {
 | 
			
		||||
        stdout.writeln("Current toml map:");
 | 
			
		||||
        stdout.writeln(TomlDocument.loadSync(tomlFilePath).toMap());
 | 
			
		||||
        final dartboardData = DartboardData.fromToml(tomlFilePath);
 | 
			
		||||
        dartboardData.miscList.forEach(stdout.writeln);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,9 +21,10 @@ class DartboardData {
 | 
			
		||||
    final tomlData = TomlDocument.loadSync(tomlFilePath).toMap();
 | 
			
		||||
 | 
			
		||||
    final List<DartboardExperience> exps = tomlData.entries
 | 
			
		||||
        .where((e) => e.value is List && (e.value as List).firstOrNull is Map)
 | 
			
		||||
        .where((e) => DartboardExperience.filter(e.value))
 | 
			
		||||
        .map((MapEntry<String, dynamic> expsEntry) {
 | 
			
		||||
          final String subsection = tomlData['${expsEntry.key}_name'] as String? ?? '';
 | 
			
		||||
          final String subsection =
 | 
			
		||||
              tomlData['${expsEntry.key}_name'] as String? ?? _getSubsectionFromKey(expsEntry.key);
 | 
			
		||||
          final exps = (expsEntry.value as List).map((e) => e as Map<String, dynamic>).toList();
 | 
			
		||||
          return exps.map((exp) {
 | 
			
		||||
            final TomlLocalDate? start = exp['start'] as TomlLocalDate?;
 | 
			
		||||
@ -35,34 +36,47 @@ class DartboardData {
 | 
			
		||||
                start: start == null ? null : DateTime(start.date.year, start.date.month),
 | 
			
		||||
                end: end == null ? null : DateTime(end.date.year, end.date.month),
 | 
			
		||||
              ),
 | 
			
		||||
              attributes: (exp['attributes'] as List).map((e) => DartboardText(content: e as String)).toList(),
 | 
			
		||||
              attributes: (exp['attributes'] as List)
 | 
			
		||||
                  .where((e) => e is String && e.isNotEmpty)
 | 
			
		||||
                  .map((e) => DartboardText(content: e as String))
 | 
			
		||||
                  .toList(),
 | 
			
		||||
              location: exp['location'] as String?,
 | 
			
		||||
            );
 | 
			
		||||
          }).toList();
 | 
			
		||||
        })
 | 
			
		||||
        .expand((i) => i)
 | 
			
		||||
        .toList();
 | 
			
		||||
    final List<DartboardMisc> misc =
 | 
			
		||||
        tomlData.entries.where((e) => DartboardMisc.filter(e.value)).map((MapEntry<String, dynamic> listEntry) {
 | 
			
		||||
      final String subsection = tomlData['${listEntry.key}_name'] as String? ?? _getSubsectionFromKey(listEntry.key);
 | 
			
		||||
      return DartboardMisc(
 | 
			
		||||
        subsection: subsection,
 | 
			
		||||
        attributes: (listEntry.value as List)
 | 
			
		||||
            .map((e) => (e as Map<String, dynamic>)['attributes'] as List)
 | 
			
		||||
            .expand((i) => i)
 | 
			
		||||
            .where((e) => e is String && e.isNotEmpty)
 | 
			
		||||
            .map((e) => DartboardText(content: e as String))
 | 
			
		||||
            .toList(),
 | 
			
		||||
      );
 | 
			
		||||
    }).toList();
 | 
			
		||||
    return DartboardData(
 | 
			
		||||
      fullName: tomlData['full_name'] as String,
 | 
			
		||||
      phoneNumber: tomlData['phone_number'] as String,
 | 
			
		||||
      email: tomlData['email'] as String,
 | 
			
		||||
      imagePath: tomlData['image'] as String,
 | 
			
		||||
      phoneNumber: tomlData['phone_number'] as String?,
 | 
			
		||||
      email: tomlData['email'] as String?,
 | 
			
		||||
      imagePath: tomlData['image'] as String?,
 | 
			
		||||
      dartboardTheme: DartboardTheme.fromToml(tomlData),
 | 
			
		||||
      experiences: exps,
 | 
			
		||||
      miscList: [
 | 
			
		||||
        DartboardList(subsection: 'Skills & Interest', content: 'Proficient in flutter and other'),
 | 
			
		||||
      ],
 | 
			
		||||
      miscList: misc,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  final String fullName;
 | 
			
		||||
  final String phoneNumber;
 | 
			
		||||
  final String email;
 | 
			
		||||
  // final String address;
 | 
			
		||||
  final String imagePath;
 | 
			
		||||
  final String? phoneNumber;
 | 
			
		||||
  final String? email;
 | 
			
		||||
  final String? imagePath;
 | 
			
		||||
  final DartboardTheme dartboardTheme;
 | 
			
		||||
  final List<DartboardExperience> experiences;
 | 
			
		||||
  final List<DartboardList> miscList;
 | 
			
		||||
  final List<DartboardMisc> miscList;
 | 
			
		||||
 | 
			
		||||
  Font get font => dartboardTheme.font;
 | 
			
		||||
 | 
			
		||||
@ -86,9 +100,20 @@ class DartboardData {
 | 
			
		||||
    return exps;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Map<String, List<DartboardMisc>> get groupedMisc {
 | 
			
		||||
    final Map<String, List<DartboardMisc>> miscs = {};
 | 
			
		||||
    for (final DartboardMisc misc in miscList) {
 | 
			
		||||
      if (!miscs.containsKey(misc.subsection)) {
 | 
			
		||||
        miscs[misc.subsection] = <DartboardMisc>[];
 | 
			
		||||
      }
 | 
			
		||||
      miscs[misc.subsection]!.add(misc);
 | 
			
		||||
    }
 | 
			
		||||
    return miscs;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode {
 | 
			
		||||
    return Object.hashAll([fullName, phoneNumber, email, imagePath, ...experiences]);
 | 
			
		||||
    return Object.hashAll([fullName, phoneNumber, email, imagePath, ...experiences, ...miscList]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -99,17 +124,25 @@ class DartboardData {
 | 
			
		||||
 | 
			
		||||
class DartboardExperience {
 | 
			
		||||
  DartboardExperience({
 | 
			
		||||
    required this.subsection,
 | 
			
		||||
    required this.title,
 | 
			
		||||
    required this.attributes,
 | 
			
		||||
    required this.range,
 | 
			
		||||
    required this.subsection,
 | 
			
		||||
    this.range,
 | 
			
		||||
    this.location,
 | 
			
		||||
  });
 | 
			
		||||
  final String subsection;
 | 
			
		||||
  final String title;
 | 
			
		||||
  String? location;
 | 
			
		||||
  final List<DartboardText> attributes;
 | 
			
		||||
  final DateRange range;
 | 
			
		||||
  final DateRange? range;
 | 
			
		||||
  String? location;
 | 
			
		||||
 | 
			
		||||
  static bool filter(dynamic e) {
 | 
			
		||||
    if (e is! List || !e.every((f) => f is Map)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    final List<Map<String, dynamic>> entries = e.map((f) => f as Map<String, dynamic>).toList();
 | 
			
		||||
    return entries.every((f) => f['title'] is String && f['attributes'] is List && f.keys.length >= 2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode {
 | 
			
		||||
@ -153,19 +186,29 @@ class DateRange {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DartboardList {
 | 
			
		||||
  DartboardList({required this.subsection, required this.content});
 | 
			
		||||
class DartboardMisc {
 | 
			
		||||
  DartboardMisc({required this.subsection, required this.attributes});
 | 
			
		||||
 | 
			
		||||
  final String subsection;
 | 
			
		||||
  final String content;
 | 
			
		||||
  final List<DartboardText> attributes;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode {
 | 
			
		||||
    return Object.hashAll([subsection, content]);
 | 
			
		||||
    return Object.hashAll([subsection, ...attributes]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) {
 | 
			
		||||
    return super.hashCode == other.hashCode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static bool filter(dynamic e) {
 | 
			
		||||
    if (e is! List || e.firstOrNull is! Map) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    final entries = (e.first as Map).entries;
 | 
			
		||||
    return entries.length == 1 && entries.first.key == 'attributes';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DartboardTheme {
 | 
			
		||||
@ -277,5 +320,13 @@ class DartboardText {
 | 
			
		||||
String _getSubsectionFromKey(String key) {
 | 
			
		||||
  switch (key) {
 | 
			
		||||
    case 'exp':
 | 
			
		||||
      return 'Experience';
 | 
			
		||||
    case 'misc':
 | 
			
		||||
      return 'Miscelaneous';
 | 
			
		||||
    case 'edu':
 | 
			
		||||
      return 'Education';
 | 
			
		||||
    case '':
 | 
			
		||||
    default:
 | 
			
		||||
      return '';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,101 +0,0 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:dartboard_resume/dartboard_parser.dart';
 | 
			
		||||
import 'package:dartboard_resume/dartboard_widgets.dart';
 | 
			
		||||
import 'package:pdf/pdf.dart';
 | 
			
		||||
import 'package:pdf/widgets.dart';
 | 
			
		||||
 | 
			
		||||
Page generatePdfPage({required DartboardData dartboardData, required int renderNs}) {
 | 
			
		||||
  final List<Widget> groupedExperienceList = dartboardData.groupedExperiences.entries.map<Widget>(
 | 
			
		||||
    (entry) {
 | 
			
		||||
      final String subsection = entry.key;
 | 
			
		||||
      final List<DartboardExperience> experiences = entry.value;
 | 
			
		||||
      return Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          Row(
 | 
			
		||||
            children: [
 | 
			
		||||
              Text(
 | 
			
		||||
                subsection,
 | 
			
		||||
                style: dartboardData.subheaderTextStyle
 | 
			
		||||
                    .merge(const TextStyle(fontSize: 18))
 | 
			
		||||
                    .apply(color: const PdfColorGrey(0.2)),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          Container(height: 2, width: 200, color: const PdfColorGrey(0.7)),
 | 
			
		||||
          ...experiences.map(
 | 
			
		||||
            (DartboardExperience exp) => Column(
 | 
			
		||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
              children: [
 | 
			
		||||
                Row(
 | 
			
		||||
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(exp.title, style: dartboardData.subheaderTextStyle.apply(color: const PdfColorGrey(0.3))),
 | 
			
		||||
                    Text(
 | 
			
		||||
                      exp.range.toString(),
 | 
			
		||||
                      style: dartboardData.subheaderTextStyle.apply(color: const PdfColorGrey(0.42)),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                ...exp.attributes.map(
 | 
			
		||||
                  (a) => Text(
 | 
			
		||||
                    "${dartboardData.dartboardTheme.bulletPoint} $a",
 | 
			
		||||
                    style: dartboardData.defaultTextStyle.apply(
 | 
			
		||||
                      color: const PdfColorGrey(0.55),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                SizedBox(height: 20),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          DartboardFooter(dartboardData: dartboardData, renderNs: renderNs),
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  ).toList();
 | 
			
		||||
  return Page(
 | 
			
		||||
    pageTheme: const PageTheme(pageFormat: PdfPageFormat.standard),
 | 
			
		||||
    build: (Context context) {
 | 
			
		||||
      return Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          SizedBox(
 | 
			
		||||
            height: 120,
 | 
			
		||||
            width: double.infinity,
 | 
			
		||||
            child: Stack(
 | 
			
		||||
              children: [
 | 
			
		||||
                Positioned(
 | 
			
		||||
                  left: 0,
 | 
			
		||||
                  child: Container(
 | 
			
		||||
                    height: 100,
 | 
			
		||||
                    width: 100,
 | 
			
		||||
                    decoration: BoxDecoration(
 | 
			
		||||
                      shape: BoxShape.circle,
 | 
			
		||||
                      image: DecorationImage(
 | 
			
		||||
                        fit: BoxFit.contain,
 | 
			
		||||
                        image: MemoryImage(
 | 
			
		||||
                          File(dartboardData.imagePath).readAsBytesSync(),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                Center(
 | 
			
		||||
                  child: Column(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      Text(dartboardData.fullName, style: dartboardData.headerTextStyle),
 | 
			
		||||
                      Text(dartboardData.phoneNumber, style: dartboardData.headerTextStyle),
 | 
			
		||||
                      Text(dartboardData.email, style: dartboardData.headerTextStyle),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          Container(height: 20),
 | 
			
		||||
          ...groupedExperienceList,
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -10,7 +10,9 @@ class DartboardFooter extends StatelessWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(Context context) {
 | 
			
		||||
    final double renderTimeMs = renderNs.toDouble() / 1000.0;
 | 
			
		||||
    return Row(
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: const EdgeInsets.only(top: 8.0),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        mainAxisAlignment: MainAxisAlignment.spaceAround,
 | 
			
		||||
        children: [
 | 
			
		||||
          Text(
 | 
			
		||||
@ -22,6 +24,7 @@ class DartboardFooter extends StatelessWidget {
 | 
			
		||||
            style: dartboardData.defaultTextStyle.copyWith(fontSize: 10),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -40,24 +43,27 @@ class DartboardExperienceEntry extends StatelessWidget {
 | 
			
		||||
          crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
			
		||||
          children: [
 | 
			
		||||
            Column(
 | 
			
		||||
            Padding(
 | 
			
		||||
              padding: const EdgeInsets.only(left: 4.0),
 | 
			
		||||
              child: Column(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                children: [
 | 
			
		||||
                  Text(exp.title, style: dartboardData.subheaderTextStyle.apply(color: const PdfColorGrey(0.3))),
 | 
			
		||||
                  Container(height: 1, width: 60, color: const PdfColorGrey(0.75)),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            Column(
 | 
			
		||||
              crossAxisAlignment: CrossAxisAlignment.end,
 | 
			
		||||
              children: [
 | 
			
		||||
                Text(
 | 
			
		||||
                  exp.range.toString(),
 | 
			
		||||
                  style: dartboardData.defaultTextStyle.apply(color: const PdfColorGrey(0.42)),
 | 
			
		||||
                  style: dartboardData.defaultTextStyle.copyWith(color: const PdfColorGrey(0.42), fontSize: 8.0),
 | 
			
		||||
                ),
 | 
			
		||||
                if (exp.location != null)
 | 
			
		||||
                  Text(
 | 
			
		||||
                    exp.location!,
 | 
			
		||||
                    style: dartboardData.defaultTextStyle.apply(color: const PdfColorGrey(0.42)),
 | 
			
		||||
                    style: dartboardData.defaultTextStyle.copyWith(color: const PdfColorGrey(0.42), fontSize: 8.0),
 | 
			
		||||
                  ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
@ -73,15 +79,86 @@ class DartboardExperienceEntry extends StatelessWidget {
 | 
			
		||||
                color: const PdfColorGrey(0.55),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            padding: const EdgeInsets.only(bottom: 4.0),
 | 
			
		||||
            padding: const EdgeInsets.only(left: 8.0, bottom: 4.0),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        SizedBox(height: 20),
 | 
			
		||||
        SizedBox(height: 12),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DartboardMiscEntry extends StatelessWidget {
 | 
			
		||||
  DartboardMiscEntry({required this.dartboardData, required this.misc});
 | 
			
		||||
  final DartboardData dartboardData;
 | 
			
		||||
  final DartboardMisc misc;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(Context context) {
 | 
			
		||||
    return Column(
 | 
			
		||||
      crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
      children: [
 | 
			
		||||
        ...misc.attributes.map(
 | 
			
		||||
          (a) => Padding(
 | 
			
		||||
            padding: const EdgeInsets.only(left: 8.0, bottom: 4.0),
 | 
			
		||||
            child: DartboardTextWithLink(
 | 
			
		||||
              stringSections: a.toTextLinkList(),
 | 
			
		||||
              bulletString: dartboardData.dartboardTheme.bulletPoint,
 | 
			
		||||
              style: dartboardData.defaultTextStyle.apply(
 | 
			
		||||
                color: const PdfColorGrey(0.55),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        SizedBox(height: 12),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class UrlFootnotes {
 | 
			
		||||
  // Factory constructor to return the single instance
 | 
			
		||||
  factory UrlFootnotes() {
 | 
			
		||||
    return _instance;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Private constructor
 | 
			
		||||
  UrlFootnotes._privateConstructor();
 | 
			
		||||
 | 
			
		||||
  // Static field to hold the single instance of the class
 | 
			
		||||
  static final UrlFootnotes _instance = UrlFootnotes._privateConstructor();
 | 
			
		||||
 | 
			
		||||
  // Field to hold the number
 | 
			
		||||
  int _numUrls = 0;
 | 
			
		||||
  List<Widget> _urlWidgets = [];
 | 
			
		||||
  TextStyle? _style;
 | 
			
		||||
 | 
			
		||||
  // ignore: avoid_setters_without_getters
 | 
			
		||||
  set style(TextStyle style) => _style = style;
 | 
			
		||||
 | 
			
		||||
  int add({required String url}) {
 | 
			
		||||
    _numUrls += 1;
 | 
			
		||||
    if (_style == null) {
 | 
			
		||||
      throw Exception('Must provide text style for urls');
 | 
			
		||||
    }
 | 
			
		||||
    _urlWidgets.add(Footnote(number: _numUrls, style: _style!, url: url));
 | 
			
		||||
    print(_urlWidgets);
 | 
			
		||||
    return _numUrls;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<Widget> get footnotes {
 | 
			
		||||
    final widgets = [..._urlWidgets];
 | 
			
		||||
    reset();
 | 
			
		||||
    return widgets;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Method to reset the number
 | 
			
		||||
  void reset() {
 | 
			
		||||
    _numUrls = 0;
 | 
			
		||||
    _urlWidgets = [];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO this lays out long text lines on a newline after a uilink rather than soft wrapping it.
 | 
			
		||||
class DartboardTextWithLink extends StatelessWidget {
 | 
			
		||||
  DartboardTextWithLink({this.bulletString, required this.stringSections, this.style});
 | 
			
		||||
@ -98,24 +175,83 @@ class DartboardTextWithLink extends StatelessWidget {
 | 
			
		||||
        Text(bulletString == null ? '' : '$bulletString ', style: style),
 | 
			
		||||
        SizedBox(
 | 
			
		||||
          width: DartboardTheme.width - DartboardTheme.margin * 2 - 20.0,
 | 
			
		||||
          child: Wrap(
 | 
			
		||||
          child: RichText(
 | 
			
		||||
            text: TextSpan(
 | 
			
		||||
              children: [
 | 
			
		||||
                ...stringSections.map((s) {
 | 
			
		||||
                  return TextSpan(text: s.text, style: style);
 | 
			
		||||
                  // switch (s.type) {
 | 
			
		||||
                  //   case DartboardTextType.normal:
 | 
			
		||||
                  //     return [Text(s.text, style: style)];
 | 
			
		||||
                  //   case DartboardTextType.linkText:
 | 
			
		||||
                  //     final number = UrlFootnotes().add(url: s.url!);
 | 
			
		||||
                  //     return [
 | 
			
		||||
                  //       Text(s.text, style: style),
 | 
			
		||||
                  //       UrlLink(
 | 
			
		||||
                  //         destination: s.url!,
 | 
			
		||||
                  //         child: Padding(
 | 
			
		||||
                  //           padding: const EdgeInsets.only(bottom: 4.0, left: 2.0, right: 2.0),
 | 
			
		||||
                  //           child: Text(
 | 
			
		||||
                  //             number.toString(),
 | 
			
		||||
                  //             style: style?.copyWith(color: PdfColors.blue, fontSize: 8),
 | 
			
		||||
                  //           ),
 | 
			
		||||
                  //         ),
 | 
			
		||||
                  //       ),
 | 
			
		||||
                  //     ];
 | 
			
		||||
                  // }
 | 
			
		||||
                }),
 | 
			
		||||
                ...stringSections.map((s) {
 | 
			
		||||
                  switch (s.type) {
 | 
			
		||||
                    case DartboardTextType.normal:
 | 
			
		||||
                    return Text(s.text, style: style);
 | 
			
		||||
                      return null;
 | 
			
		||||
                    case DartboardTextType.linkText:
 | 
			
		||||
                    return UrlLink(
 | 
			
		||||
                      destination: s.url!,
 | 
			
		||||
                      final number = UrlFootnotes().add(url: s.url!);
 | 
			
		||||
                      return TextSpan(
 | 
			
		||||
                        text: ' [$number]',
 | 
			
		||||
                        style: style?.copyWith(color: PdfColors.blue, fontSize: 8),
 | 
			
		||||
                      );
 | 
			
		||||
                    // return UrlLink(
 | 
			
		||||
                    //   destination: s.url!,
 | 
			
		||||
                    //   child: Padding(
 | 
			
		||||
                    //     padding: const EdgeInsets.only(bottom: 4.0, left: 2.0, right: 2.0),
 | 
			
		||||
                    //     child: Text(
 | 
			
		||||
                    //       number.toString(),
 | 
			
		||||
                    //       style: style?.copyWith(color: PdfColors.blue, fontSize: 8),
 | 
			
		||||
                    //     ),
 | 
			
		||||
                    //   ),
 | 
			
		||||
                    // );
 | 
			
		||||
                  }
 | 
			
		||||
                }).nonNulls,
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Footnote extends StatelessWidget {
 | 
			
		||||
  Footnote({required this.number, required this.url, required this.style});
 | 
			
		||||
 | 
			
		||||
  final String url;
 | 
			
		||||
  final int number;
 | 
			
		||||
  final TextStyle style;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(Context context) {
 | 
			
		||||
    return Row(
 | 
			
		||||
      children: [
 | 
			
		||||
        UrlLink(
 | 
			
		||||
          destination: url,
 | 
			
		||||
          child: Text(
 | 
			
		||||
                        s.text,
 | 
			
		||||
                        style: style?.copyWith(color: PdfColors.blue),
 | 
			
		||||
            '[$number]',
 | 
			
		||||
            style: style.copyWith(color: PdfColors.blue, fontSize: 8),
 | 
			
		||||
          ),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
              }),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        Text(
 | 
			
		||||
          ' $url',
 | 
			
		||||
          style: style.copyWith(fontSize: 8),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -19,10 +19,11 @@ Future<void> renderPdf(String tomlFilePath, {bool force = false}) async {
 | 
			
		||||
    stdout.writeln("Detected change:\nRendering with new dartboard data: $lastDartboardHash");
 | 
			
		||||
    final pdfFuture = Document()..addPage(_generatePdfPage(dartboardData: dartboardData, renderNs: 0));
 | 
			
		||||
    await pdfFuture.save();
 | 
			
		||||
 | 
			
		||||
    final renderNs = DateTime.now().microsecondsSinceEpoch - start;
 | 
			
		||||
 | 
			
		||||
    final pdf = Document();
 | 
			
		||||
    pdf.addPage(_generatePdfPage(dartboardData: dartboardData, renderNs: renderNs));
 | 
			
		||||
 | 
			
		||||
    final file = File("example.pdf");
 | 
			
		||||
    final bytes = await pdf.save();
 | 
			
		||||
 | 
			
		||||
@ -30,8 +31,10 @@ Future<void> renderPdf(String tomlFilePath, {bool force = false}) async {
 | 
			
		||||
    file.writeAsBytesSync(bytes);
 | 
			
		||||
    stdout.writeln('Reloading llpp...');
 | 
			
		||||
    Process.runSync('pkill', ['-HUP', 'llpp']);
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    UrlFootnotes().reset();
 | 
			
		||||
  } catch (e, st) {
 | 
			
		||||
    stderr.writeln('Encountered error: $e');
 | 
			
		||||
    stderr.writeln(st);
 | 
			
		||||
    try {
 | 
			
		||||
      stderr.writeln('Current toml map:\n${TomlDocument.loadSync(tomlFilePath).toMap()}');
 | 
			
		||||
    } catch (_) {
 | 
			
		||||
@ -41,6 +44,7 @@ Future<void> renderPdf(String tomlFilePath, {bool force = false}) async {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Page _generatePdfPage({required DartboardData dartboardData, required int renderNs}) {
 | 
			
		||||
  UrlFootnotes().style = dartboardData.defaultTextStyle;
 | 
			
		||||
  final List<Widget> groupedExperienceList = dartboardData.groupedExperiences.entries.map<Widget>(
 | 
			
		||||
    (entry) {
 | 
			
		||||
      final String subsection = entry.key;
 | 
			
		||||
@ -57,17 +61,49 @@ Page _generatePdfPage({required DartboardData dartboardData, required int render
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          Row(
 | 
			
		||||
            children: [
 | 
			
		||||
              Container(height: 2, width: 200, color: const PdfColorGrey(0.7)),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          SizedBox(height: 3),
 | 
			
		||||
          ...experiences.map(
 | 
			
		||||
            (DartboardExperience exp) => DartboardExperienceEntry(dartboardData: dartboardData, exp: exp),
 | 
			
		||||
          ),
 | 
			
		||||
          DartboardFooter(dartboardData: dartboardData, renderNs: renderNs),
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  ).toList();
 | 
			
		||||
  return Page(
 | 
			
		||||
    pageTheme: const PageTheme(pageFormat: PdfPageFormat.standard),
 | 
			
		||||
  final List<Widget> groupedMiscList = dartboardData.groupedMisc.entries.map<Widget>((entry) {
 | 
			
		||||
    final String subsection = entry.key;
 | 
			
		||||
    final List<DartboardMisc> miscs = entry.value;
 | 
			
		||||
    return Column(
 | 
			
		||||
      children: [
 | 
			
		||||
        Row(
 | 
			
		||||
          children: [
 | 
			
		||||
            Text(
 | 
			
		||||
              subsection,
 | 
			
		||||
              style: dartboardData.subheaderTextStyle
 | 
			
		||||
                  .merge(const TextStyle(fontSize: 18))
 | 
			
		||||
                  .apply(color: const PdfColorGrey(0.2)),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
        Row(
 | 
			
		||||
          children: [
 | 
			
		||||
            Container(height: 2, width: 200, color: const PdfColorGrey(0.7)),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
        SizedBox(height: 3),
 | 
			
		||||
        ...miscs.map(
 | 
			
		||||
          (DartboardMisc misc) => DartboardMiscEntry(dartboardData: dartboardData, misc: misc),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }).toList();
 | 
			
		||||
  return FullPage(
 | 
			
		||||
    pageTheme: PageTheme(
 | 
			
		||||
        buildBackground: (_) => Container(color: dartboardData.backgroundColor), pageFormat: PdfPageFormat.standard),
 | 
			
		||||
    build: (Context context) {
 | 
			
		||||
      return
 | 
			
		||||
          // FullPage(
 | 
			
		||||
@ -75,11 +111,24 @@ Page _generatePdfPage({required DartboardData dartboardData, required int render
 | 
			
		||||
          // child:
 | 
			
		||||
          Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          if (dartboardData.imagePath == null)
 | 
			
		||||
            Center(
 | 
			
		||||
              child: Column(
 | 
			
		||||
                children: [
 | 
			
		||||
                  Text(dartboardData.fullName, style: dartboardData.headerTextStyle),
 | 
			
		||||
                  if (dartboardData.phoneNumber != null)
 | 
			
		||||
                    Text(dartboardData.phoneNumber!, style: dartboardData.headerTextStyle),
 | 
			
		||||
                  if (dartboardData.email != null) Text(dartboardData.email!, style: dartboardData.headerTextStyle),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            )
 | 
			
		||||
          else
 | 
			
		||||
            SizedBox(
 | 
			
		||||
            height: 120,
 | 
			
		||||
              height: 100,
 | 
			
		||||
              width: double.infinity,
 | 
			
		||||
              child: Stack(
 | 
			
		||||
                children: [
 | 
			
		||||
                  if (dartboardData.imagePath != null)
 | 
			
		||||
                    Positioned(
 | 
			
		||||
                      left: 0,
 | 
			
		||||
                      child: Container(
 | 
			
		||||
@ -90,7 +139,7 @@ Page _generatePdfPage({required DartboardData dartboardData, required int render
 | 
			
		||||
                          image: DecorationImage(
 | 
			
		||||
                            fit: BoxFit.contain,
 | 
			
		||||
                            image: MemoryImage(
 | 
			
		||||
                          File(dartboardData.imagePath).readAsBytesSync(),
 | 
			
		||||
                              File(dartboardData.imagePath!).readAsBytesSync(),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
@ -100,16 +149,21 @@ Page _generatePdfPage({required DartboardData dartboardData, required int render
 | 
			
		||||
                    child: Column(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(dartboardData.fullName, style: dartboardData.headerTextStyle),
 | 
			
		||||
                      Text(dartboardData.phoneNumber, style: dartboardData.headerTextStyle),
 | 
			
		||||
                      Text(dartboardData.email, style: dartboardData.headerTextStyle),
 | 
			
		||||
                        if (dartboardData.phoneNumber != null)
 | 
			
		||||
                          Text(dartboardData.phoneNumber!, style: dartboardData.headerTextStyle),
 | 
			
		||||
                        if (dartboardData.email != null)
 | 
			
		||||
                          Text(dartboardData.email!, style: dartboardData.headerTextStyle),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          Container(height: 20),
 | 
			
		||||
          ...groupedExperienceList,
 | 
			
		||||
          ...groupedMiscList,
 | 
			
		||||
          // this is quirky and gets evaluated before the actual footnotes get added up, so the original adding is done in the prerender
 | 
			
		||||
          ...UrlFootnotes().footnotes,
 | 
			
		||||
          DartboardFooter(dartboardData: dartboardData, renderNs: renderNs),
 | 
			
		||||
        ],
 | 
			
		||||
        // ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user