Showing
6 changed files
with
339 additions
and
34 deletions
pdf/web_example/web/calendar.dart
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +import 'package:intl/intl.dart'; | ||
| 18 | +import 'package:pdf/pdf.dart'; | ||
| 19 | +import 'package:pdf/widgets.dart'; | ||
| 20 | + | ||
| 21 | +class Calendar extends StatelessWidget { | ||
| 22 | + Calendar({ | ||
| 23 | + this.date, | ||
| 24 | + this.month, | ||
| 25 | + this.year, | ||
| 26 | + }); | ||
| 27 | + | ||
| 28 | + final DateTime date; | ||
| 29 | + | ||
| 30 | + final int month; | ||
| 31 | + | ||
| 32 | + final int year; | ||
| 33 | + | ||
| 34 | + Widget title( | ||
| 35 | + Context context, | ||
| 36 | + DateTime date, | ||
| 37 | + ) { | ||
| 38 | + return Container( | ||
| 39 | + width: double.infinity, | ||
| 40 | + child: Text( | ||
| 41 | + DateFormat.yMMMM().format(date), | ||
| 42 | + style: const TextStyle( | ||
| 43 | + color: PdfColors.deepPurple, | ||
| 44 | + fontSize: 40, | ||
| 45 | + ), | ||
| 46 | + ), | ||
| 47 | + ); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + Widget header( | ||
| 51 | + Context context, | ||
| 52 | + DateTime date, | ||
| 53 | + ) { | ||
| 54 | + return Container( | ||
| 55 | + color: PdfColors.blue200, | ||
| 56 | + padding: const EdgeInsets.all(8.0), | ||
| 57 | + child: Text( | ||
| 58 | + DateFormat.EEEE().format(date), | ||
| 59 | + style: const TextStyle( | ||
| 60 | + fontSize: 15, | ||
| 61 | + ), | ||
| 62 | + ), | ||
| 63 | + ); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + Widget day( | ||
| 67 | + Context context, | ||
| 68 | + DateTime date, | ||
| 69 | + bool currentMonth, | ||
| 70 | + bool currentDay, | ||
| 71 | + ) { | ||
| 72 | + String text = '${date.day}'; | ||
| 73 | + TextStyle style = const TextStyle(); | ||
| 74 | + PdfColor color = PdfColors.grey300; | ||
| 75 | + | ||
| 76 | + if (currentDay) { | ||
| 77 | + style = TextStyle(color: PdfColors.red); | ||
| 78 | + color = PdfColors.lightBlue50; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + if (!currentMonth) { | ||
| 82 | + style = TextStyle(color: PdfColors.grey); | ||
| 83 | + color = PdfColors.grey100; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + if (date.day == 1) { | ||
| 87 | + text += ' ' + DateFormat.MMM().format(date); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + return Container( | ||
| 91 | + padding: const EdgeInsets.all(4), | ||
| 92 | + color: color, | ||
| 93 | + child: Text( | ||
| 94 | + text, | ||
| 95 | + textAlign: TextAlign.right, | ||
| 96 | + style: style, | ||
| 97 | + ), | ||
| 98 | + ); | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + @override | ||
| 102 | + Widget build(Context context) { | ||
| 103 | + final DateTime _date = date ?? DateTime.now(); | ||
| 104 | + final int _year = year ?? _date.year; | ||
| 105 | + final int _month = month ?? _date.month; | ||
| 106 | + | ||
| 107 | + final DateTime start = DateTime(_year, _month, 1, 12); | ||
| 108 | + final DateTime end = DateTime(_year, _month + 1, 1, 12).subtract( | ||
| 109 | + const Duration(days: 1), | ||
| 110 | + ); | ||
| 111 | + | ||
| 112 | + final int startId = start.weekday - 1; | ||
| 113 | + final int endId = end.difference(start).inDays + startId + 1; | ||
| 114 | + | ||
| 115 | + final Row head = Row( | ||
| 116 | + mainAxisSize: MainAxisSize.max, | ||
| 117 | + children: List<Widget>.generate(7, (int index) { | ||
| 118 | + final DateTime d = start.add(Duration(days: index - startId)); | ||
| 119 | + return Expanded( | ||
| 120 | + child: Container( | ||
| 121 | + foregroundDecoration: BoxDecoration( | ||
| 122 | + border: BoxBorder( | ||
| 123 | + color: PdfColors.black, | ||
| 124 | + top: true, | ||
| 125 | + left: true, | ||
| 126 | + right: index % 7 == 6, | ||
| 127 | + bottom: true, | ||
| 128 | + ), | ||
| 129 | + ), | ||
| 130 | + child: header(context, d), | ||
| 131 | + ), | ||
| 132 | + ); | ||
| 133 | + }), | ||
| 134 | + ); | ||
| 135 | + | ||
| 136 | + final GridView body = GridView( | ||
| 137 | + crossAxisCount: 7, | ||
| 138 | + children: List<Widget>.generate(42, (int index) { | ||
| 139 | + final DateTime d = start.add(Duration(days: index - startId)); | ||
| 140 | + final bool currentMonth = index >= startId && index < endId; | ||
| 141 | + final bool currentDay = d.year == _date.year && | ||
| 142 | + d.month == _date.month && | ||
| 143 | + d.day == _date.day; | ||
| 144 | + return Container( | ||
| 145 | + foregroundDecoration: BoxDecoration( | ||
| 146 | + border: BoxBorder( | ||
| 147 | + color: PdfColors.black, | ||
| 148 | + left: true, | ||
| 149 | + right: index % 7 == 6, | ||
| 150 | + bottom: true, | ||
| 151 | + ), | ||
| 152 | + ), | ||
| 153 | + child: day(context, d, currentMonth, currentDay), | ||
| 154 | + ); | ||
| 155 | + }), | ||
| 156 | + ); | ||
| 157 | + | ||
| 158 | + return Container( | ||
| 159 | + padding: const EdgeInsets.all(20), | ||
| 160 | + child: Column( | ||
| 161 | + mainAxisAlignment: MainAxisAlignment.start, | ||
| 162 | + mainAxisSize: MainAxisSize.min, | ||
| 163 | + children: <Widget>[ | ||
| 164 | + title(context, DateTime(_year, _month)), | ||
| 165 | + head, | ||
| 166 | + Flexible(flex: 1, child: body), | ||
| 167 | + ], | ||
| 168 | + ), | ||
| 169 | + ); | ||
| 170 | + } | ||
| 171 | +} |
| @@ -8,17 +8,87 @@ | @@ -8,17 +8,87 @@ | ||
| 8 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 8 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 9 | <title>Pdf Web Example</title> | 9 | <title>Pdf Web Example</title> |
| 10 | <link rel="stylesheet" href="styles.css"> | 10 | <link rel="stylesheet" href="styles.css"> |
| 11 | - <script defer src="main.dart.js"></script> | 11 | + <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.3.200/pdf.min.js"></script> |
| 12 | + <script src="main.dart.js"></script> | ||
| 13 | + <script> | ||
| 14 | + async function displayPdf(pdf) { | ||
| 15 | + let doc = await pdfjsLib.getDocument(pdf).promise; | ||
| 16 | + document.getElementById('page_count').innerText = doc.numPages; | ||
| 17 | + let metadata = await doc.getMetadata(); | ||
| 18 | + let title = metadata.info.Title || 'Untitled'; | ||
| 19 | + document.getElementById('title').innerText = title; | ||
| 20 | + document.title = title; | ||
| 21 | + | ||
| 22 | + let author = metadata.info.Author || ''; | ||
| 23 | + let producer = metadata.info.Producer || ''; | ||
| 24 | + document.getElementById('author').innerText = author + ' | ' + producer; | ||
| 25 | + | ||
| 26 | + let currentPage = 1; | ||
| 27 | + | ||
| 28 | + document.getElementById('prev').addEventListener('click', () => { | ||
| 29 | + if (currentPage <= 1) { | ||
| 30 | + return; | ||
| 31 | + } | ||
| 32 | + currentPage--; | ||
| 33 | + renderPage(doc, currentPage); | ||
| 34 | + }); | ||
| 35 | + | ||
| 36 | + document.getElementById('next').addEventListener('click', () => { | ||
| 37 | + if (currentPage >= doc.numPages) { | ||
| 38 | + return; | ||
| 39 | + } | ||
| 40 | + currentPage++; | ||
| 41 | + renderPage(doc, currentPage); | ||
| 42 | + }); | ||
| 43 | + | ||
| 44 | + renderPage(doc, currentPage); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + async function renderPage(doc, pageNum) { | ||
| 48 | + document.getElementById('page_num').innerText = pageNum; | ||
| 49 | + | ||
| 50 | + let page = await doc.getPage(pageNum); | ||
| 51 | + let viewport = page.getViewport({ scale: 1 }); | ||
| 52 | + let canvas = document.getElementById('container'); | ||
| 53 | + canvas.width = viewport.width; | ||
| 54 | + canvas.height = viewport.height; | ||
| 55 | + | ||
| 56 | + let renderContext = { | ||
| 57 | + canvasContext: canvas.getContext('2d'), | ||
| 58 | + viewport: viewport | ||
| 59 | + }; | ||
| 60 | + | ||
| 61 | + await page.render(renderContext).promise; | ||
| 62 | + | ||
| 63 | + document.getElementById('toolbar-loading').style.display = 'none'; | ||
| 64 | + document.getElementById('toolbar-content').style.display = ''; | ||
| 65 | + document.getElementById('container').style.display = ''; | ||
| 66 | + document.getElementById('author').style.display = ''; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + function ready() { | ||
| 70 | + // Dart is fully loaded | ||
| 71 | + let pdf = buildPdf(); | ||
| 72 | + displayPdf(pdf); | ||
| 73 | + }; | ||
| 74 | + </script> | ||
| 12 | </head> | 75 | </head> |
| 13 | 76 | ||
| 14 | <body> | 77 | <body> |
| 15 | - <div> | ||
| 16 | - <button id="generate">Generate</button> | 78 | + <div class="toolbar"> |
| 79 | + <div id="toolbar-loading">Loading ...</div> | ||
| 80 | + <div id="toolbar-content" style="display:none;"> | ||
| 81 | + <button id="prev">Previous</button> | ||
| 82 | + <button id="next">Next</button> | ||
| 83 | + | ||
| 84 | + <span>Page: <span id="page_num"></span> / <span id="page_count"></span></span> | ||
| 85 | + <span id="title"></span> | ||
| 17 | </div> | 86 | </div> |
| 18 | - | ||
| 19 | - <div id="container"> | ||
| 20 | - <object id="doc" type="application/pdf"></object> | ||
| 21 | </div> | 87 | </div> |
| 88 | + | ||
| 89 | + | ||
| 90 | + <canvas id="container" style="display:none;"></canvas> | ||
| 91 | + <div id="author" style="display:none;"></div> | ||
| 22 | </body> | 92 | </body> |
| 23 | 93 | ||
| 24 | </html> | 94 | </html> |
| 1 | -import 'dart:convert'; | 1 | +/* |
| 2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +import 'dart:async'; | ||
| 2 | import 'dart:html'; | 18 | import 'dart:html'; |
| 19 | +import 'dart:js' as js; | ||
| 20 | +import 'dart:typed_data'; | ||
| 3 | 21 | ||
| 4 | import 'package:pdf/pdf.dart'; | 22 | import 'package:pdf/pdf.dart'; |
| 5 | import 'package:pdf/widgets.dart'; | 23 | import 'package:pdf/widgets.dart'; |
| 6 | 24 | ||
| 7 | -void main() { | ||
| 8 | - final ButtonElement generateButton = querySelector('#generate'); | 25 | +import 'calendar.dart'; |
| 9 | 26 | ||
| 10 | - generateButton.onClick.listen((_) async { | ||
| 11 | - final String data = Uri.encodeComponent(base64.encode(buildPdf())); | 27 | +Uint8List buildPdf() { |
| 28 | + final Document pdf = Document(title: 'My Document', author: 'David PHAM-VAN'); | ||
| 12 | 29 | ||
| 13 | - final ObjectElement doc = querySelector('#doc'); | ||
| 14 | - doc.data = 'data:application/pdf;base64,$data'; | ||
| 15 | - }); | ||
| 16 | -} | ||
| 17 | - | ||
| 18 | -List<int> buildPdf() { | ||
| 19 | - final Document pdf = Document(); | ||
| 20 | - | ||
| 21 | - pdf.addPage(Page(build: (Context ctx) { | 30 | + pdf.addPage(Page( |
| 31 | + build: (Context ctx) { | ||
| 22 | return Container( | 32 | return Container( |
| 23 | width: double.infinity, | 33 | width: double.infinity, |
| 24 | height: double.infinity, | 34 | height: double.infinity, |
| @@ -29,7 +39,33 @@ List<int> buildPdf() { | @@ -29,7 +39,33 @@ List<int> buildPdf() { | ||
| 29 | ), | 39 | ), |
| 30 | ), | 40 | ), |
| 31 | ); | 41 | ); |
| 32 | - })); | 42 | + }, |
| 43 | + )); | ||
| 44 | + | ||
| 45 | + pdf.addPage(Page( | ||
| 46 | + pageFormat: PdfPageFormat.a4.landscape, | ||
| 47 | + build: (Context context) => Calendar(), | ||
| 48 | + )); | ||
| 33 | 49 | ||
| 34 | - return pdf.save(); | 50 | + pdf.addPage(Page( |
| 51 | + pageFormat: PdfPageFormat.a4.landscape, | ||
| 52 | + build: (Context context) => Calendar( | ||
| 53 | + month: DateTime.now().month + 1, | ||
| 54 | + ), | ||
| 55 | + )); | ||
| 56 | + | ||
| 57 | + pdf.addPage(Page( | ||
| 58 | + build: (Context ctx) { | ||
| 59 | + return Center(child: PdfLogo()); | ||
| 60 | + }, | ||
| 61 | + )); | ||
| 62 | + | ||
| 63 | + return Uint8List.fromList(pdf.save()); | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +void main() { | ||
| 67 | + js.context['buildPdf'] = buildPdf; | ||
| 68 | + Timer.run(() { | ||
| 69 | + js.context.callMethod('ready'); | ||
| 70 | + }); | ||
| 35 | } | 71 | } |
| @@ -10,18 +10,44 @@ body { | @@ -10,18 +10,44 @@ body { | ||
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | #container { | 12 | #container { |
| 13 | - left: 10px; | ||
| 14 | - right: 10px; | ||
| 15 | - width: auto; | ||
| 16 | - bottom: 10px; | ||
| 17 | - top: 49px; | ||
| 18 | - display: block; | ||
| 19 | - position: absolute; | ||
| 20 | - border: 1px dotted red; | 13 | + border: 1px solid rgb(37, 37, 37); |
| 14 | + box-shadow: 3px 3px 5px rgb(129, 129, 129); | ||
| 15 | + margin: 20px; | ||
| 21 | } | 16 | } |
| 22 | 17 | ||
| 23 | -#doc { | ||
| 24 | - width: 100%; | ||
| 25 | - height: 100%; | ||
| 26 | - display: block; | 18 | +.toolbar { |
| 19 | + background-color: rgb(82, 82, 82); | ||
| 20 | + padding: 10px; | ||
| 21 | + color: #fff; | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +button { | ||
| 25 | + height: 50px; | ||
| 26 | + width: 100px; | ||
| 27 | + background-color: #5cb8ff; | ||
| 28 | + border: 1px solid #4f90ff; | ||
| 29 | + display: inline-block; | ||
| 30 | + cursor: pointer; | ||
| 31 | + color: #ffffff; | ||
| 32 | + font-size: 17px; | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +button:hover { | ||
| 36 | + background-color: #2688ff; | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +button:active { | ||
| 40 | + background-color: #1a64be; | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +#title { | ||
| 44 | + margin-left: 2rem; | ||
| 45 | + font-size: 1.8em; | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +#author { | ||
| 49 | + margin-left: 20px; | ||
| 50 | + margin-bottom: 20px; | ||
| 51 | + color: rgb(65, 72, 77); | ||
| 52 | + font-size: 0.7em; | ||
| 27 | } | 53 | } |
-
Please register or login to post a comment