David PHAM-VAN

Improve Annotations

# Changelog
## 1.6.0
- Improve Annotations
## 1.5.0
- Fix Align debug painting
... ...
... ... @@ -19,125 +19,138 @@
part of pdf;
class PdfAnnot extends PdfObject {
PdfAnnot._create(PdfPage pdfPage,
{String type,
this.content,
this.srcRect,
@required this.subtype,
this.dest,
this.destRect,
this.border,
this.url,
this.name})
: assert(subtype != null),
super(pdfPage.pdfDocument, type ?? '/Annot') {
PdfAnnot(this.pdfPage, this.annot)
: assert(annot != null),
super(pdfPage.pdfDocument, '/Annot') {
pdfPage.annotations.add(this);
}
/// Creates a text annotation
/// @param rect coordinates
/// @param s Text for this annotation
/// Create a text annotation
@deprecated
factory PdfAnnot.text(
PdfPage pdfPage, {
@required PdfRect rect,
@required String content,
PdfBorder border,
}) =>
PdfAnnot._create(
PdfAnnot(
pdfPage,
subtype: '/Text',
srcRect: rect,
PdfAnnotText(
rect: rect,
content: content,
border: border,
);
/// Creates a link annotation
/// @param srcRect coordinates
/// @param dest Destination for this link. The page will fit the display.
/// @param destRect Rectangle describing what part of the page to be displayed
/// (must be in User Coordinates)
factory PdfAnnot.link(
PdfPage pdfPage, {
@required PdfRect srcRect,
@required PdfPage dest,
PdfRect destRect,
PdfBorder border,
}) =>
PdfAnnot._create(
pdfPage,
subtype: '/Link',
srcRect: srcRect,
dest: dest,
destRect: destRect,
border: border,
);
));
/// Creates an external link annotation
@deprecated
factory PdfAnnot.urlLink(
PdfPage pdfPage, {
@required PdfRect rect,
@required String dest,
PdfBorder border,
}) =>
PdfAnnot._create(
PdfAnnot(
pdfPage,
subtype: '/Link',
srcRect: rect,
PdfAnnotUrlLink(
rect: rect,
url: dest,
border: border,
);
));
/// Creates a link annotation to a named destination
@deprecated
factory PdfAnnot.namedLink(
PdfPage pdfPage, {
@required PdfRect rect,
@required String dest,
PdfBorder border,
}) =>
PdfAnnot._create(
PdfAnnot(
pdfPage,
subtype: '/Link',
srcRect: rect,
name: dest,
PdfAnnotNamedLink(
rect: rect,
dest: dest,
border: border,
),
);
/// The subtype of the outline, ie text, note, etc
final String subtype;
/// The annotation content
final PdfAnnotBase annot;
/// The size of the annotation
final PdfRect srcRect;
/// The page where the annotation will display
final PdfPage pdfPage;
/// The text of a text annotation
final String content;
/// Output the annotation
///
/// @param os OutputStream to send the object to
@override
void _prepare() {
super._prepare();
annot.build(pdfPage, params);
}
}
/// Link to the Destination page
final PdfObject dest;
enum PdfAnnotFlags {
invisible,
hidden,
print,
noZoom,
noRotate,
noView,
readOnly,
locked,
toggleNoView,
lockedContent
}
/// If destRect is null then this is the region of the destination page shown.
/// Otherwise they are ignored.
final PdfRect destRect;
abstract class PdfAnnotBase {
const PdfAnnotBase({
@required this.subtype,
@required this.rect,
this.border,
this.content,
this.name,
this.flags,
this.date,
this.color,
}) : assert(subtype != null),
assert(rect != null);
/// The subtype of the outline, ie text, note, etc
final String subtype;
final PdfRect rect;
/// the border for this annotation
final PdfBorder border;
/// The external url for a link
final String url;
/// The text of a text annotation
final String content;
/// The internal name for a link
final String name;
/// Output the annotation
///
/// @param os OutputStream to send the object to
@override
void _prepare() {
super._prepare();
/// Flags specifying various characteristics of the annotation
final Set<PdfAnnotFlags> flags;
/// Last modification date
final DateTime date;
/// Color
final PdfColor color;
int get flagValue => flags
?.map<int>((PdfAnnotFlags e) => 1 >> e.index)
?.reduce((int a, int b) => a | b);
@protected
@mustCallSuper
void build(PdfPage page, Map<String, PdfStream> params) {
params['/Subtype'] = PdfStream.string(subtype);
params['/Rect'] = PdfStream()
..putNumArray(
<double>[srcRect.left, srcRect.bottom, srcRect.right, srcRect.top]);
..putNumArray(<double>[rect.left, rect.bottom, rect.right, rect.top]);
params['/P'] = page.ref();
// handle the border
if (border == null) {
... ... @@ -146,39 +159,194 @@ class PdfAnnot extends PdfObject {
params['/BS'] = border.ref();
}
// Now the annotation subtypes
if (subtype == '/Text') {
if (content != null) {
params['/Contents'] = PdfStream()..putLiteral(content);
} else if (subtype == '/Link') {
if (url != null) {
}
if (name != null) {
params['/NM'] = PdfStream()..putLiteral(name);
}
if (flags != null) {
params['/F'] = PdfStream.intNum(flagValue);
}
if (date != null) {
params['/M'] = PdfStream()..putDate(date);
}
if (color != null) {
if (color is PdfColorCmyk) {
final PdfColorCmyk k = color;
params['/C'] = PdfStream()
..putNumList(<double>[k.cyan, k.magenta, k.yellow, k.black]);
} else {
params['/C'] = PdfStream()
..putNumList(<double>[color.red, color.green, color.blue]);
}
}
}
}
class PdfAnnotText extends PdfAnnotBase {
/// Create a text annotation
const PdfAnnotText({
@required PdfRect rect,
@required String content,
PdfBorder border,
String name,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
}) : super(
subtype: '/Text',
rect: rect,
border: border,
content: content,
name: name,
flags: flags,
date: date,
color: color,
);
}
class PdfAnnotNamedLink extends PdfAnnotBase {
/// Create a named link annotation
const PdfAnnotNamedLink({
@required PdfRect rect,
@required this.dest,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
}) : super(
subtype: '/Link',
rect: rect,
border: border,
flags: flags,
date: date,
color: color,
);
final String dest;
@override
void build(PdfPage page, Map<String, PdfStream> params) {
super.build(page, params);
params['/A'] = PdfStream()
..putDictionary(
<String, PdfStream>{
'/S': PdfStream()..putString('/GoTo'),
'/D': PdfStream()..putText(dest),
},
);
}
}
class PdfAnnotUrlLink extends PdfAnnotBase {
/// Create an url link annotation
const PdfAnnotUrlLink({
@required PdfRect rect,
@required this.url,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
}) : super(
subtype: '/Link',
rect: rect,
border: border,
flags: flags,
date: date,
color: color,
);
final String url;
@override
void build(PdfPage page, Map<String, PdfStream> params) {
super.build(page, params);
params['/A'] = PdfStream()
..putDictionary(<String, PdfStream>{
..putDictionary(
<String, PdfStream>{
'/S': PdfStream()..putString('/URI'),
'/URI': PdfStream()..putText(url),
});
} else if (name != null) {
params['/A'] = PdfStream()
..putDictionary(<String, PdfStream>{
'/S': PdfStream()..putString('/GoTo'),
'/D': PdfStream()..putText(name),
});
} else {
final List<PdfStream> dests = <PdfStream>[];
dests.add(dest.ref());
if (destRect == null) {
dests.add(PdfStream.string('/Fit'));
} else {
dests.add(PdfStream.string('/FitR '));
dests.add(PdfStream()
..putNumList(<double>[
destRect.left,
destRect.bottom,
destRect.right,
destRect.top
]));
},
);
}
params['/Dest'] = PdfStream.array(dests);
}
enum PdfAnnotHighlighting { none, invert, outline, push, toggle }
abstract class PdfAnnotWidget extends PdfAnnotBase {
/// Create an url link annotation
const PdfAnnotWidget(
PdfRect rect,
this.fieldType, {
this.fieldName,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
this.highlighting,
this.value,
}) : super(
subtype: '/Widget',
rect: rect,
border: border,
flags: flags,
date: date,
color: color,
);
final String fieldType;
final String fieldName;
final PdfAnnotHighlighting highlighting;
final PdfStream value;
@override
void build(PdfPage page, Map<String, PdfStream> params) {
super.build(page, params);
params['/FT'] = PdfStream.string(fieldType);
if (fieldName != null) {
params['/T'] = PdfStream()..putLiteral(fieldName);
}
if (value != null) {
params['/V'] = value;
}
}
}
class PdfAnnotSign extends PdfAnnotWidget {
const PdfAnnotSign(
PdfRect rect, {
String fieldName,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
PdfAnnotHighlighting highlighting,
}) : super(
rect,
'/Sig',
fieldName: fieldName,
border: border,
flags: flags,
date: date,
color: color,
highlighting: highlighting,
);
@override
void build(PdfPage page, Map<String, PdfStream> params) {
super.build(page, params);
assert(page.pdfDocument.sign != null);
params['/V'] = page.pdfDocument.sign.ref();
}
}
... ...
... ... @@ -14,6 +14,8 @@
* limitations under the License.
*/
// ignore_for_file: omit_local_variable_types
part of pdf;
class PdfCatalog extends PdfObject {
... ... @@ -67,8 +69,25 @@ class PdfCatalog extends PdfObject {
PdfStream.string(PdfDocument._PdfPageModes[pageMode.index]);
if (pdfDocument.sign != null) {
params['/Perms'] = PdfStream.dictionary(
<String, PdfStream>{'/DocMDP': pdfDocument.sign.ref()});
params['/Perms'] = PdfStream.dictionary(<String, PdfStream>{
'/DocMDP': pdfDocument.sign.ref(),
});
}
final List<PdfAnnot> widgets = <PdfAnnot>[];
for (PdfPage page in pdfDocument.pdfPageList.pages) {
for (PdfAnnot annot in page.annotations) {
if (annot.annot.subtype == '/Widget') {
widgets.add(annot);
}
}
}
if (widgets.isNotEmpty) {
params['/AcroForm'] = PdfStream.dictionary(<String, PdfStream>{
'/SigFlags': PdfStream.intNum(pdfDocument.sign?.flagsValue ?? 0),
'/Fields': PdfStream()..putObjectArray(widgets),
});
}
}
}
... ...
... ... @@ -15,6 +15,7 @@
*/
// ignore_for_file: omit_local_variable_types
// ignore_for_file: avoid_unused_constructor_parameters
part of pdf;
... ... @@ -33,13 +34,7 @@ class PDFAnnot extends PdfAnnot {
double fb,
double fr,
double ft})
: super._create(pdfPage,
type: type,
content: s,
srcRect: PdfRect.fromLTRB(l, t, r, b),
subtype: subtype,
dest: dest,
destRect: PdfRect.fromLTRB(fl, ft, fr, fb));
: super(pdfPage, PdfAnnotText(rect: PdfRect(l, b, r, t), content: s));
factory PDFAnnot.annotation(
PdfPage pdfPage, String s, double l, double b, double r, double t) =>
... ... @@ -342,12 +337,11 @@ class PDFPage extends PdfPage {
double vh = PDFAnnot.FULL_PAGE]) {
final PdfPoint xy1 = cxy(x, y + h);
final PdfPoint xy2 = cxy(x + w, y);
final PdfPoint xy3 = cxy(vx, vy + vh);
final PdfPoint xy4 = cxy(vx + vw, vy);
final PdfAnnot ob = PdfAnnot.link(this,
srcRect: PdfRect.fromLTRB(xy1.x, xy1.y, xy2.x, xy2.y),
dest: dest,
destRect: PdfRect.fromLTRB(xy3.x, xy3.y, xy4.x, xy4.y));
final PdfAnnot ob = PdfAnnot.urlLink(
this,
rect: PdfRect.fromLTRB(xy1.x, xy1.y, xy2.x, xy2.y),
dest: 'https://github.com/DavBfr/dart_pdf',
);
return ob;
}
... ...
... ... @@ -107,7 +107,7 @@ class PdfPage extends PdfObject {
// the /Contents pages object
if (contents.isNotEmpty) {
if (contents.length == 1) {
params['/Contents'] = contents[0].ref();
params['/Contents'] = contents.first.ref();
} else {
params['/Contents'] = PdfStream()..putObjectArray(contents);
}
... ...
... ... @@ -18,29 +18,33 @@
part of pdf;
@immutable
class PdfSignatureRange {
const PdfSignatureRange(this.start, this.end);
enum PdfSigFlags { signaturesExist, appendOnly }
final int start;
final int end;
}
class PdfSignature extends PdfObject {
PdfSignature(
PdfDocument pdfDocument, {
@required this.crypto,
Set<PdfSigFlags> flags,
}) : assert(crypto != null),
flags = flags ?? const <PdfSigFlags>{PdfSigFlags.signaturesExist},
super(pdfDocument, '/Sig');
abstract class PdfSignature extends PdfObject {
PdfSignature(PdfDocument pdfDocument) : super(pdfDocument, '/Sig');
final Set<PdfSigFlags> flags;
int _offsetStart;
int _offsetEnd;
final PdfSignatureBase crypto;
void preSign();
int get flagsValue => flags
.map<int>((PdfSigFlags e) => 1 >> e.index)
.reduce((int a, int b) => a | b);
void sign(PdfStream os, List<PdfSignatureRange> ranges);
int _offsetStart;
int _offsetEnd;
@override
void _write(PdfStream os) {
preSign();
crypto.preSign(params);
_offsetStart = os.offset;
_offsetStart = os.offset + '$objser $objgen obj\n'.length;
super._write(os);
_offsetEnd = os.offset;
}
... ... @@ -49,16 +53,13 @@ abstract class PdfSignature extends PdfObject {
assert(_offsetStart != null && _offsetEnd != null,
'Must reserve the object space before signing the document');
final List<PdfSignatureRange> ranges = <PdfSignatureRange>[
PdfSignatureRange(0, _offsetStart),
PdfSignatureRange(_offsetEnd, os.offset),
];
crypto.sign(os, params, _offsetStart, _offsetEnd);
}
}
sign(os, ranges);
final PdfStream signature = PdfStream();
super._write(signature);
abstract class PdfSignatureBase {
void preSign(Map<String, PdfStream> params);
assert(signature.offset == _offsetEnd - _offsetStart);
os.output().replaceRange(_offsetStart, _offsetEnd, signature.output());
}
void sign(PdfStream os, Map<String, PdfStream> params, int offsetStart,
int offsetEnd);
}
... ...
... ... @@ -39,7 +39,7 @@ class Anchor extends SingleChildWidget {
if (description != null) {
final Vector3 rb = mat.transform3(Vector3(box.right, box.top, 0));
final PdfRect ibox = PdfRect.fromLTRB(lt.x, lt.y, rb.x, rb.y);
PdfAnnot.text(context.page, content: description, rect: ibox);
PdfAnnot(context.page, PdfAnnotText(rect: ibox, content: description));
}
}
}
... ... @@ -62,10 +62,12 @@ class AnnotationLink extends AnnotationBuilder {
@override
void build(Context context, PdfRect box) {
PdfAnnot.namedLink(
PdfAnnot(
context.page,
PdfAnnotNamedLink(
rect: localToGlobal(context, box),
dest: destination,
),
);
}
}
... ... @@ -77,10 +79,63 @@ class AnnotationUrl extends AnnotationBuilder {
@override
void build(Context context, PdfRect box) {
PdfAnnot.urlLink(
PdfAnnot(
context.page,
PdfAnnotUrlLink(
rect: localToGlobal(context, box),
dest: destination,
url: destination,
),
);
}
}
class AnnotationSignature extends AnnotationBuilder {
AnnotationSignature(
this.crypto, {
this.name,
this.signFlags,
this.border,
this.flags,
this.date,
this.color,
this.highlighting,
}) : assert(crypto != null);
final Set<PdfSigFlags> signFlags;
final PdfSignatureBase crypto;
final String name;
final PdfBorder border;
final Set<PdfAnnotFlags> flags;
final DateTime date;
final PdfColor color;
final PdfAnnotHighlighting highlighting;
@override
void build(Context context, PdfRect box) {
context.document.sign ??= PdfSignature(
context.document,
crypto: crypto,
flags: signFlags,
);
PdfAnnot(
context.page,
PdfAnnotSign(
localToGlobal(context, box),
fieldName: name,
border: border,
flags: flags,
date: date,
color: color,
highlighting: highlighting,
),
);
}
}
... ... @@ -117,3 +172,30 @@ class UrlLink extends Annotation {
: assert(child != null),
super(child: child, builder: AnnotationUrl(destination));
}
class Signature extends Annotation {
Signature({
@required Widget child,
@required PdfSignatureBase crypto,
@required String name,
Set<PdfSigFlags> signFlags,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
PdfAnnotHighlighting highlighting,
}) : assert(child != null),
assert(crypto != null),
super(
child: child,
builder: AnnotationSignature(
crypto,
signFlags: signFlags,
name: name,
border: border,
flags: flags,
date: date,
color: color,
highlighting: highlighting,
));
}
... ...
... ... @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl
homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf
repository: https://github.com/DavBfr/dart_pdf
issue_tracker: https://github.com/DavBfr/dart_pdf/issues
version: 1.5.0
version: 1.6.0
environment:
sdk: ">=2.3.0 <3.0.0"
... ...
... ... @@ -29,18 +29,35 @@ void main() {
final PdfPage page1 =
PdfPage(pdf, pageFormat: const PdfPageFormat(500, 300));
pdf.pdfNames.addDest('target', page1, posY: 100);
final PdfGraphics g = page.getGraphics();
PdfAnnot.text(page,
content: 'Hello', rect: const PdfRect(100, 100, 50, 50));
PdfAnnot(
page,
const PdfAnnotText(
rect: PdfRect(100, 100, 50, 50),
content: 'Hello',
),
);
PdfAnnot.link(page, dest: page1, srcRect: const PdfRect(100, 150, 50, 50));
PdfAnnot(
page,
const PdfAnnotNamedLink(
dest: 'target',
rect: PdfRect(100, 150, 50, 50),
),
);
g.drawRect(100, 150, 50, 50);
g.strokePath();
PdfAnnot.urlLink(page,
rect: const PdfRect(100, 250, 50, 50),
dest: 'https://github.com/DavBfr/dart_pdf/');
PdfAnnot(
page,
const PdfAnnotUrlLink(
rect: PdfRect(100, 250, 50, 50),
url: 'https://github.com/DavBfr/dart_pdf/',
),
);
g.drawRect(100, 250, 50, 50);
g.strokePath();
... ...