David PHAM-VAN

Add Hyperlink widgets

@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 * Improve MultiPage Widget 5 * Improve MultiPage Widget
6 * Convert GridView to a SpanningWidget 6 * Convert GridView to a SpanningWidget
7 * Add all Material Colors 7 * Add all Material Colors
  8 +* Add Hyperlink widgets
8 9
9 # 1.3.3 10 # 1.3.3
10 * Fix a bug with the RichText Widget 11 * Fix a bug with the RichText Widget
@@ -41,6 +41,7 @@ part 'src/formxobject.dart'; @@ -41,6 +41,7 @@ part 'src/formxobject.dart';
41 part 'src/graphics.dart'; 41 part 'src/graphics.dart';
42 part 'src/image.dart'; 42 part 'src/image.dart';
43 part 'src/info.dart'; 43 part 'src/info.dart';
  44 +part 'src/names.dart';
44 part 'src/object.dart'; 45 part 'src/object.dart';
45 part 'src/object_stream.dart'; 46 part 'src/object_stream.dart';
46 part 'src/outline.dart'; 47 part 'src/outline.dart';
@@ -24,7 +24,9 @@ class PdfAnnot extends PdfObject { @@ -24,7 +24,9 @@ class PdfAnnot extends PdfObject {
24 @required this.subtype, 24 @required this.subtype,
25 this.dest, 25 this.dest,
26 this.destRect, 26 this.destRect,
27 - this.border}) 27 + this.border,
  28 + this.url,
  29 + this.name})
28 : super(pdfPage.pdfDocument, type ?? '/Annot') { 30 : super(pdfPage.pdfDocument, type ?? '/Annot') {
29 pdfPage.annotations.add(this); 31 pdfPage.annotations.add(this);
30 } 32 }
@@ -56,6 +58,18 @@ class PdfAnnot extends PdfObject { @@ -56,6 +58,18 @@ class PdfAnnot extends PdfObject {
56 destRect: destRect, 58 destRect: destRect,
57 border: border); 59 border: border);
58 60
  61 + /// Creates an external link annotation
  62 + factory PdfAnnot.urlLink(PdfPage pdfPage,
  63 + {@required PdfRect rect, @required String dest, PdfBorder border}) =>
  64 + PdfAnnot._create(pdfPage,
  65 + subtype: '/Link', srcRect: rect, url: dest, border: border);
  66 +
  67 + /// Creates a link annotation to a named destination
  68 + factory PdfAnnot.namedLink(PdfPage pdfPage,
  69 + {@required PdfRect rect, @required String dest, PdfBorder border}) =>
  70 + PdfAnnot._create(pdfPage,
  71 + subtype: '/Link', srcRect: rect, name: dest, border: border);
  72 +
59 /// The subtype of the outline, ie text, note, etc 73 /// The subtype of the outline, ie text, note, etc
60 final String subtype; 74 final String subtype;
61 75
@@ -75,6 +89,12 @@ class PdfAnnot extends PdfObject { @@ -75,6 +89,12 @@ class PdfAnnot extends PdfObject {
75 /// the border for this annotation 89 /// the border for this annotation
76 final PdfBorder border; 90 final PdfBorder border;
77 91
  92 + /// The external url for a link
  93 + final String url;
  94 +
  95 + /// The internal name for a link
  96 + final String name;
  97 +
78 /// Output the annotation 98 /// Output the annotation
79 /// 99 ///
80 /// @param os OutputStream to send the object to 100 /// @param os OutputStream to send the object to
@@ -83,8 +103,9 @@ class PdfAnnot extends PdfObject { @@ -83,8 +103,9 @@ class PdfAnnot extends PdfObject {
83 super._prepare(); 103 super._prepare();
84 104
85 params['/Subtype'] = PdfStream.string(subtype); 105 params['/Subtype'] = PdfStream.string(subtype);
86 - params['/Rect'] = PdfStream.string(  
87 - '[${srcRect.left} ${srcRect.bottom} ${srcRect.right} ${srcRect.top}]'); 106 + params['/Rect'] = PdfStream()
  107 + ..putNumArray(
  108 + <double>[srcRect.left, srcRect.bottom, srcRect.right, srcRect.top]);
88 109
89 // handle the border 110 // handle the border
90 if (border == null) { 111 if (border == null) {
@@ -97,15 +118,35 @@ class PdfAnnot extends PdfObject { @@ -97,15 +118,35 @@ class PdfAnnot extends PdfObject {
97 if (subtype == '/Text') { 118 if (subtype == '/Text') {
98 params['/Contents'] = PdfStream()..putLiteral(content); 119 params['/Contents'] = PdfStream()..putLiteral(content);
99 } else if (subtype == '/Link') { 120 } else if (subtype == '/Link') {
  121 + if (url != null) {
  122 + params['/A'] = PdfStream()
  123 + ..putDictionary(<String, PdfStream>{
  124 + '/S': PdfStream()..putString('/URI'),
  125 + '/URI': PdfStream()..putText(url),
  126 + });
  127 + } else if (name != null) {
  128 + params['/A'] = PdfStream()
  129 + ..putDictionary(<String, PdfStream>{
  130 + '/S': PdfStream()..putString('/GoTo'),
  131 + '/D': PdfStream()..putText(name),
  132 + });
  133 + } else {
100 final List<PdfStream> dests = <PdfStream>[]; 134 final List<PdfStream> dests = <PdfStream>[];
101 dests.add(dest.ref()); 135 dests.add(dest.ref());
102 if (destRect == null) 136 if (destRect == null)
103 dests.add(PdfStream.string('/Fit')); 137 dests.add(PdfStream.string('/Fit'));
104 else { 138 else {
105 - dests.add(PdfStream.string(  
106 - '/FitR ${destRect.left} ${destRect.bottom} ${destRect.right} ${destRect.top}')); 139 + dests.add(PdfStream.string('/FitR '));
  140 + dests.add(PdfStream()
  141 + ..putNumList(<double>[
  142 + destRect.left,
  143 + destRect.bottom,
  144 + destRect.right,
  145 + destRect.top
  146 + ]));
107 } 147 }
108 params['/Dest'] = PdfStream.array(dests); 148 params['/Dest'] = PdfStream.array(dests);
109 } 149 }
110 } 150 }
  151 + }
111 } 152 }
@@ -22,7 +22,8 @@ class PdfCatalog extends PdfObject { @@ -22,7 +22,8 @@ class PdfCatalog extends PdfObject {
22 /// @param pdfPageList The [PdfPageList] object that's the root of the documents page tree 22 /// @param pdfPageList The [PdfPageList] object that's the root of the documents page tree
23 /// @param pagemode How the document should appear when opened. 23 /// @param pagemode How the document should appear when opened.
24 /// Allowed values are usenone, useoutlines, usethumbs or fullscreen. 24 /// Allowed values are usenone, useoutlines, usethumbs or fullscreen.
25 - PdfCatalog(PdfDocument pdfDocument, this.pdfPageList, this.pageMode) 25 + PdfCatalog(
  26 + PdfDocument pdfDocument, this.pdfPageList, this.pageMode, this.names)
26 : super(pdfDocument, '/Catalog'); 27 : super(pdfDocument, '/Catalog');
27 28
28 /// The pages of the document 29 /// The pages of the document
@@ -34,6 +35,9 @@ class PdfCatalog extends PdfObject { @@ -34,6 +35,9 @@ class PdfCatalog extends PdfObject {
34 /// The initial page mode 35 /// The initial page mode
35 final PdfPageMode pageMode; 36 final PdfPageMode pageMode;
36 37
  38 + /// The initial page mode
  39 + final PdfNames names;
  40 +
37 /// @param os OutputStream to send the object to 41 /// @param os OutputStream to send the object to
38 @override 42 @override
39 void _prepare() { 43 void _prepare() {
@@ -46,6 +50,9 @@ class PdfCatalog extends PdfObject { @@ -46,6 +50,9 @@ class PdfCatalog extends PdfObject {
46 params['/Outlines'] = outlines.ref(); 50 params['/Outlines'] = outlines.ref();
47 } 51 }
48 52
  53 + // the Names object
  54 + params['/Names'] = names.ref();
  55 +
49 // the /PageMode setting 56 // the /PageMode setting
50 params['/PageMode'] = 57 params['/PageMode'] =
51 PdfStream.string(PdfDocument._PdfPageModes[pageMode.index]); 58 PdfStream.string(PdfDocument._PdfPageModes[pageMode.index]);
@@ -89,9 +89,9 @@ class PDFBorder extends PdfBorder { @@ -89,9 +89,9 @@ class PDFBorder extends PdfBorder {
89 89
90 @deprecated 90 @deprecated
91 class PDFCatalog extends PdfCatalog { 91 class PDFCatalog extends PdfCatalog {
92 - PDFCatalog(  
93 - PdfDocument pdfDocument, PdfPageList pdfPageList, PdfPageMode pageMode)  
94 - : super(pdfDocument, pdfPageList, pageMode); 92 + PDFCatalog(PdfDocument pdfDocument, PdfPageList pdfPageList,
  93 + PdfPageMode pageMode, PdfNames names)
  94 + : super(pdfDocument, pdfPageList, pageMode, names);
95 } 95 }
96 96
97 @deprecated 97 @deprecated
@@ -51,7 +51,8 @@ class PdfDocument { @@ -51,7 +51,8 @@ class PdfDocument {
51 51
52 // Now create some standard objects 52 // Now create some standard objects
53 pdfPageList = PdfPageList(this); 53 pdfPageList = PdfPageList(this);
54 - catalog = PdfCatalog(this, pdfPageList, pageMode); 54 + pdfNames = PdfNames(this);
  55 + catalog = PdfCatalog(this, pdfPageList, pageMode, pdfNames);
55 } 56 }
56 57
57 /// This is used to allocate objects a unique serial number in the document. 58 /// This is used to allocate objects a unique serial number in the document.
@@ -70,6 +71,9 @@ class PdfDocument { @@ -70,6 +71,9 @@ class PdfDocument {
70 /// This is the Pages object, which is required by each Pdf Document 71 /// This is the Pages object, which is required by each Pdf Document
71 PdfPageList pdfPageList; 72 PdfPageList pdfPageList;
72 73
  74 + /// The name dictionary
  75 + PdfNames pdfNames;
  76 +
73 /// This is the Outline object, which is optional 77 /// This is the Outline object, which is optional
74 PdfOutline _outline; 78 PdfOutline _outline;
75 79
  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 +part of pdf;
  18 +
  19 +class PdfNames extends PdfObject {
  20 + /// This constructs a Pdf Name object
  21 + PdfNames(PdfDocument pdfDocument) : super(pdfDocument);
  22 +
  23 + final List<PdfStream> _dests = <PdfStream>[];
  24 +
  25 + void addDest(
  26 + String name,
  27 + PdfPage page, {
  28 + double posX,
  29 + double posY,
  30 + double posZ,
  31 + }) {
  32 + assert(page.pdfDocument == pdfDocument);
  33 + assert(name != null);
  34 +
  35 + _dests.add(PdfStream()..putText(name));
  36 + _dests.add(PdfStream()
  37 + ..putDictionary(<String, PdfStream>{
  38 + '/D': PdfStream()
  39 + ..putArray(<PdfStream>[
  40 + page.ref(),
  41 + PdfStream.string('/XYZ'),
  42 + posX == null ? PdfStream.string('null') : PdfStream.num(posX),
  43 + posY == null ? PdfStream.string('null') : PdfStream.num(posY),
  44 + posZ == null ? PdfStream.string('null') : PdfStream.num(posZ),
  45 + ]),
  46 + }));
  47 + }
  48 +
  49 + @override
  50 + void _prepare() {
  51 + super._prepare();
  52 +
  53 + params['/Dests'] = PdfStream()
  54 + ..putDictionary(<String, PdfStream>{
  55 + '/Names': PdfStream()..putArray(_dests),
  56 + });
  57 + }
  58 +}
@@ -23,6 +23,7 @@ import 'package:meta/meta.dart'; @@ -23,6 +23,7 @@ import 'package:meta/meta.dart';
23 import 'package:pdf/pdf.dart'; 23 import 'package:pdf/pdf.dart';
24 import 'package:vector_math/vector_math_64.dart'; 24 import 'package:vector_math/vector_math_64.dart';
25 25
  26 +part 'widgets/annotations.dart';
26 part 'widgets/basic.dart'; 27 part 'widgets/basic.dart';
27 part 'widgets/clip.dart'; 28 part 'widgets/clip.dart';
28 part 'widgets/container.dart'; 29 part 'widgets/container.dart';
  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 +part of widget;
  18 +
  19 +class Anchor extends SingleChildWidget {
  20 + Anchor({Widget child, @required this.name, this.description})
  21 + : assert(name != null),
  22 + super(child: child);
  23 +
  24 + final String name;
  25 +
  26 + final String description;
  27 +
  28 + @override
  29 + void paint(Context context) {
  30 + super.paint(context);
  31 + paintChild(context);
  32 +
  33 + final Matrix4 mat = context.canvas.getTransform();
  34 + final Vector3 lt = mat.transform3(Vector3(box.left, box.bottom, 0));
  35 + context.document.pdfNames.addDest(name, context.page, posY: lt.y);
  36 +
  37 + if (description != null) {
  38 + final Vector3 rb = mat.transform3(Vector3(box.right, box.top, 0));
  39 + final PdfRect ibox = PdfRect.fromLTRB(lt.x, lt.y, rb.x, rb.y);
  40 + PdfAnnot.text(context.page, content: description, rect: ibox);
  41 + }
  42 + }
  43 +}
  44 +
  45 +class _Annotation extends SingleChildWidget {
  46 + _Annotation({Widget child}) : super(child: child);
  47 +
  48 + @override
  49 + void debugPaint(Context context) {
  50 + context.canvas
  51 + ..setFillColor(PdfColors.pink)
  52 + ..drawRect(box.x, box.y, box.width, box.height)
  53 + ..fillPath();
  54 + }
  55 +
  56 + @override
  57 + void paint(Context context) {
  58 + super.paint(context);
  59 + paintChild(context);
  60 + }
  61 +}
  62 +
  63 +class Link extends _Annotation {
  64 + Link({@required Widget child, this.destination})
  65 + : assert(child != null),
  66 + super(child: child);
  67 +
  68 + final String destination;
  69 +
  70 + @override
  71 + void paint(Context context) {
  72 + super.paint(context);
  73 +
  74 + if (destination != null) {
  75 + final Matrix4 mat = context.canvas.getTransform();
  76 + final Vector3 lt = mat.transform3(Vector3(box.left, box.bottom, 0));
  77 + final Vector3 rb = mat.transform3(Vector3(box.right, box.top, 0));
  78 + final PdfRect ibox = PdfRect.fromLTRB(lt.x, lt.y, rb.x, rb.y);
  79 + PdfAnnot.namedLink(
  80 + context.page,
  81 + rect: ibox,
  82 + dest: destination,
  83 + );
  84 + }
  85 + }
  86 +}
  87 +
  88 +class UrlLink extends _Annotation {
  89 + UrlLink({@required Widget child, @required this.destination})
  90 + : assert(child != null),
  91 + assert(destination != null),
  92 + super(child: child);
  93 +
  94 + final String destination;
  95 +
  96 + @override
  97 + void paint(Context context) {
  98 + super.paint(context);
  99 +
  100 + final Matrix4 mat = context.canvas.getTransform();
  101 + final Vector3 lt = mat.transform3(Vector3(box.left, box.bottom, 0));
  102 + final Vector3 rb = mat.transform3(Vector3(box.right, box.top, 0));
  103 + final PdfRect ibox = PdfRect.fromLTRB(lt.x, lt.y, rb.x, rb.y);
  104 + PdfAnnot.urlLink(
  105 + context.page,
  106 + rect: ibox,
  107 + dest: destination,
  108 + );
  109 + }
  110 +}
@@ -36,6 +36,12 @@ void main() { @@ -36,6 +36,12 @@ void main() {
36 g.drawRect(100, 150, 50, 50); 36 g.drawRect(100, 150, 50, 50);
37 g.strokePath(); 37 g.strokePath();
38 38
  39 + PdfAnnot.urlLink(page,
  40 + rect: const PdfRect(100, 250, 50, 50),
  41 + dest: 'https://github.com/DavBfr/dart_pdf/');
  42 + g.drawRect(100, 250, 50, 50);
  43 + g.strokePath();
  44 +
39 final File file = File('annotations.pdf'); 45 final File file = File('annotations.pdf');
40 file.writeAsBytesSync(pdf.save()); 46 file.writeAsBytesSync(pdf.save());
41 }); 47 });
@@ -51,7 +51,10 @@ void main() { @@ -51,7 +51,10 @@ void main() {
51 width: 2)), 51 width: 2)),
52 child: Text('Hello World', 52 child: Text('Hello World',
53 textScaleFactor: 2, textAlign: TextAlign.center)), 53 textScaleFactor: 2, textAlign: TextAlign.center)),
54 - Align(alignment: Alignment.topLeft, child: Text('Left align')), 54 + Align(
  55 + alignment: Alignment.topLeft,
  56 + child:
  57 + Link(destination: 'anchor', child: Text('Left align'))),
55 Padding(padding: const EdgeInsets.all(5)), 58 Padding(padding: const EdgeInsets.all(5)),
56 Row( 59 Row(
57 mainAxisAlignment: MainAxisAlignment.spaceEvenly, 60 mainAxisAlignment: MainAxisAlignment.spaceEvenly,
@@ -133,6 +136,7 @@ void main() { @@ -133,6 +136,7 @@ void main() {
133 <String>['York Steak House', 'Outi Vuorinen', 'Finland'], 136 <String>['York Steak House', 'Outi Vuorinen', 'Finland'],
134 <String>['Weathervane', 'Else Jeremiassen', 'Iceland'], 137 <String>['Weathervane', 'Else Jeremiassen', 'Iceland'],
135 ]), 138 ]),
  139 + Anchor(name: 'anchor', child: Text('Anchor')),
136 ])); 140 ]));
137 141
138 pdf.addPage(Page( 142 pdf.addPage(Page(
@@ -172,7 +176,13 @@ void main() { @@ -172,7 +176,13 @@ void main() {
172 ), 176 ),
173 ], 177 ],
174 ), 178 ),
175 - )) 179 + )),
  180 + Positioned(
  181 + right: 10,
  182 + bottom: 10,
  183 + child: UrlLink(
  184 + child: Text('dart_pdf'),
  185 + destination: 'https://github.com/DavBfr/dart_pdf/'))
176 ]))); 186 ])));
177 187
178 final File file = File('widgets.pdf'); 188 final File file = File('widgets.pdf');