David PHAM-VAN

Add AcroForm widgets

... ... @@ -52,3 +52,4 @@ ref
cache_*
.vscode
package.json
... ...
... ... @@ -3,6 +3,7 @@
## 1.13.0
- Implement different border radius on all corners
- Add AcroForm widgets
## 1.12.0
... ...
... ... @@ -50,6 +50,7 @@ part 'src/font_metrics.dart';
part 'src/formxobject.dart';
part 'src/function.dart';
part 'src/graphic_state.dart';
part 'src/graphic_stream.dart';
part 'src/graphics.dart';
part 'src/image.dart';
part 'src/info.dart';
... ...
... ... @@ -42,20 +42,45 @@ class PdfAnnot extends PdfObject {
}
enum PdfAnnotFlags {
/// 1
invisible,
/// 2
hidden,
/// 3
print,
/// 4
noZoom,
/// 5
noRotate,
/// 6
noView,
/// 7
readOnly,
/// 8
locked,
/// 9
toggleNoView,
lockedContent
/// 10
lockedContent,
}
enum PdfAnnotApparence {
normal,
rollover,
down,
}
abstract class PdfAnnotBase {
const PdfAnnotBase({
PdfAnnotBase({
@required this.subtype,
@required this.rect,
this.border,
... ... @@ -90,9 +115,66 @@ abstract class PdfAnnotBase {
/// Color
final PdfColor color;
int get flagValue => flags
?.map<int>((PdfAnnotFlags e) => 1 >> e.index)
?.reduce((int a, int b) => a | b);
final Map<String, PdfDataType> _appearances = <String, PdfDataType>{};
int get flagValue {
if (flags == null || flags.isEmpty) {
return 0;
}
return flags
.map<int>((PdfAnnotFlags e) => 1 << e.index)
.reduce((int a, int b) => a | b);
}
PdfGraphics appearance(
PdfDocument pdfDocument,
PdfAnnotApparence type, {
String name,
Matrix4 matrix,
PdfRect boundingBox,
}) {
final PdfGraphicXObject s = PdfGraphicXObject(pdfDocument, '/Form');
String n;
switch (type) {
case PdfAnnotApparence.normal:
n = '/N';
break;
case PdfAnnotApparence.rollover:
n = '/R';
break;
case PdfAnnotApparence.down:
n = '/D';
break;
}
if (name == null) {
_appearances[n] = s.ref();
} else {
if (_appearances[n] is! PdfDict) {
_appearances[n] = PdfDict();
}
final PdfDict d = _appearances[n];
d[name] = s.ref();
}
if (matrix != null) {
s.params['/Matrix'] = PdfArray.fromNum(<double>[
matrix[0],
matrix[1],
matrix[4],
matrix[5],
matrix[12],
matrix[13]
]);
}
final PdfRect bbox =
boundingBox ?? PdfRect.fromPoints(PdfPoint.zero, rect.size);
s.params['/BBox'] =
PdfArray.fromNum(<double>[bbox.x, bbox.y, bbox.width, bbox.height]);
final PdfGraphics g = PdfGraphics(s, s.buf);
return g;
}
@protected
@mustCallSuper
... ... @@ -118,7 +200,7 @@ abstract class PdfAnnotBase {
params['/NM'] = PdfSecString.fromString(object, name);
}
if (flags != null) {
if (flags != null && flags.isNotEmpty) {
params['/F'] = PdfNum(flagValue);
}
... ... @@ -127,13 +209,14 @@ abstract class PdfAnnotBase {
}
if (color != null) {
if (color is PdfColorCmyk) {
final PdfColorCmyk k = color;
params['/C'] =
PdfArray.fromNum(<double>[k.cyan, k.magenta, k.yellow, k.black]);
} else {
params['/C'] =
PdfArray.fromNum(<double>[color.red, color.green, color.blue]);
params['/C'] = PdfColorType(color);
}
if (_appearances.isNotEmpty) {
params['/AP'] = PdfDict(_appearances);
if (_appearances['/N'] is PdfDict) {
final PdfDict n = _appearances['/N'];
params['/AS'] = PdfName(n.values.keys.first);
}
}
}
... ... @@ -141,7 +224,7 @@ abstract class PdfAnnotBase {
class PdfAnnotText extends PdfAnnotBase {
/// Create a text annotation
const PdfAnnotText({
PdfAnnotText({
@required PdfRect rect,
@required String content,
PdfBorder border,
... ... @@ -163,7 +246,7 @@ class PdfAnnotText extends PdfAnnotBase {
class PdfAnnotNamedLink extends PdfAnnotBase {
/// Create a named link annotation
const PdfAnnotNamedLink({
PdfAnnotNamedLink({
@required PdfRect rect,
@required this.dest,
PdfBorder border,
... ... @@ -195,7 +278,7 @@ class PdfAnnotNamedLink extends PdfAnnotBase {
class PdfAnnotUrlLink extends PdfAnnotBase {
/// Create an url link annotation
const PdfAnnotUrlLink({
PdfAnnotUrlLink({
@required PdfRect rect,
@required this.url,
PdfBorder border,
... ... @@ -228,15 +311,16 @@ class PdfAnnotUrlLink extends PdfAnnotBase {
enum PdfAnnotHighlighting { none, invert, outline, push, toggle }
abstract class PdfAnnotWidget extends PdfAnnotBase {
/// Create an url link annotation
const PdfAnnotWidget(
PdfRect rect,
this.fieldType, {
/// Create a widget annotation
PdfAnnotWidget({
@required PdfRect rect,
@required this.fieldType,
this.fieldName,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
this.backgroundColor,
this.highlighting,
}) : super(
subtype: '/Widget',
... ... @@ -253,6 +337,8 @@ abstract class PdfAnnotWidget extends PdfAnnotBase {
final PdfAnnotHighlighting highlighting;
final PdfColor backgroundColor;
@override
void build(PdfPage page, PdfObject object, PdfDict params) {
super.build(page, object, params);
... ... @@ -262,12 +348,45 @@ abstract class PdfAnnotWidget extends PdfAnnotBase {
if (fieldName != null) {
params['/T'] = PdfSecString.fromString(object, fieldName);
}
final PdfDict mk = PdfDict();
if (color != null) {
mk.values['/BC'] = PdfColorType(color);
}
if (backgroundColor != null) {
mk.values['/BG'] = PdfColorType(backgroundColor);
}
if (mk.values.isNotEmpty) {
params['/MK'] = mk;
}
if (highlighting != null) {
switch (highlighting) {
case PdfAnnotHighlighting.none:
params['/H'] = const PdfName('/N');
break;
case PdfAnnotHighlighting.invert:
params['/H'] = const PdfName('/I');
break;
case PdfAnnotHighlighting.outline:
params['/H'] = const PdfName('/O');
break;
case PdfAnnotHighlighting.push:
params['/H'] = const PdfName('/P');
break;
case PdfAnnotHighlighting.toggle:
params['/H'] = const PdfName('/T');
break;
}
}
}
}
class PdfAnnotSign extends PdfAnnotWidget {
const PdfAnnotSign(
PdfRect rect, {
PdfAnnotSign({
@required PdfRect rect,
String fieldName,
PdfBorder border,
Set<PdfAnnotFlags> flags,
... ... @@ -275,8 +394,8 @@ class PdfAnnotSign extends PdfAnnotWidget {
PdfColor color,
PdfAnnotHighlighting highlighting,
}) : super(
rect,
'/Sig',
rect: rect,
fieldType: '/Sig',
fieldName: fieldName,
border: border,
flags: flags,
... ... @@ -292,3 +411,284 @@ class PdfAnnotSign extends PdfAnnotWidget {
params['/V'] = page.pdfDocument.sign.ref();
}
}
enum PdfFieldFlags {
/// 1 - If set, the user may not change the value of the field.
readOnly,
/// 2 - If set, the field shall have a value at the time it is exported by
/// a submit-form action.
mandatory,
/// 3 - If set, the field shall not be exported by a submit-form action.
noExport,
/// 4
reserved4,
/// 5
reserved5,
/// 6
reserved6,
/// 7
reserved7,
/// 8
reserved8,
/// 9
reserved9,
/// 10
reserved10,
/// 11
reserved11,
/// 12
reserved12,
/// 13 - If set, the field may contain multiple lines of text; if clear,
/// the field’s text shall be restricted to a single line.
multiline,
/// 14 - If set, the field is intended for entering a secure password that
/// should not be echoed visibly to the screen. Characters typed from
/// the keyboard shall instead be echoed in some unreadable form, such
/// as asterisks or bullet characters.
password,
/// 15 - If set, exactly one radio button shall be selected at all times.
noToggleToOff,
/// 16 - If set, the field is a set of radio buttons; if clear,
/// the field is a check box.
radio,
/// 17 - If set, the field is a pushbutton that does not retain
/// a permanent value.
pushButton,
/// 18 - If set, the field is a combo box; if clear, the field is a list box.
combo,
/// 19 - If set, the combo box shall include an editable text box as well
/// as a drop-down list
edit,
/// 20 - If set, the field’s option items shall be sorted alphabetically.
sort,
/// 21 - If set, the text entered in the field represents the pathname
/// of a file whose contents shall be submitted as the value of the field.
fileSelect,
/// 22 - If set, more than one of the field’s option items may be selected
/// simultaneously
multiSelect,
/// 23 - If set, text entered in the field shall not be spell-checked.
doNotSpellCheck,
/// 24 - If set, the field shall not scroll to accommodate more text
/// than fits within its annotation rectangle.
doNotScroll,
/// 25 - If set, the field shall be automatically divided into as many
/// equally spaced positions, or combs, as the value of MaxLen,
/// and the text is laid out into those combs.
comb,
/// 26 - If set, a group of radio buttons within a radio button field
/// that use the same value for the on state will turn on and off in unison.
radiosInUnison,
/// 27 - If set, the new value shall be committed as soon as a selection
/// is made.
commitOnSelChange,
}
class PdfFormField extends PdfAnnotWidget {
PdfFormField({
@required String fieldType,
@required PdfRect rect,
String fieldName,
this.alternateName,
this.mappingName,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
PdfColor backgroundColor,
PdfAnnotHighlighting highlighting,
this.fieldFlags,
}) : super(
rect: rect,
fieldType: fieldType,
fieldName: fieldName,
border: border,
flags: flags,
date: date,
backgroundColor: backgroundColor,
color: color,
highlighting: highlighting,
);
final String alternateName;
final String mappingName;
final Set<PdfFieldFlags> fieldFlags;
int get fieldFlagsValue {
if (fieldFlags == null || fieldFlags.isEmpty) {
return 0;
}
return fieldFlags
.map<int>((PdfFieldFlags e) => 1 << e.index)
.reduce((int a, int b) => a | b);
}
@override
void build(PdfPage page, PdfObject object, PdfDict params) {
super.build(page, object, params);
if (alternateName != null) {
params['/TU'] = PdfSecString.fromString(object, alternateName);
}
if (mappingName != null) {
params['/TM'] = PdfSecString.fromString(object, mappingName);
}
params['/Ff'] = PdfNum(fieldFlagsValue);
}
}
enum PdfTextFieldAlign { left, center, right }
class PdfTextField extends PdfFormField {
PdfTextField({
@required PdfRect rect,
String fieldName,
String alternateName,
String mappingName,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
PdfColor backgroundColor,
PdfAnnotHighlighting highlighting,
Set<PdfFieldFlags> fieldFlags,
this.value,
this.defaultValue,
this.maxLength,
@required this.font,
@required this.fontSize,
@required this.textColor,
this.textAlign,
}) : assert(fontSize != null),
assert(textColor != null),
assert(font != null),
super(
rect: rect,
fieldType: '/Tx',
fieldName: fieldName,
border: border,
flags: flags,
date: date,
color: color,
backgroundColor: backgroundColor,
highlighting: highlighting,
alternateName: alternateName,
mappingName: mappingName,
fieldFlags: fieldFlags,
);
final int maxLength;
final String value;
final String defaultValue;
final PdfFont font;
final double fontSize;
final PdfColor textColor;
final PdfTextFieldAlign textAlign;
@override
void build(PdfPage page, PdfObject object, PdfDict params) {
super.build(page, object, params);
if (maxLength != null) {
params['/MaxLen'] = PdfNum(maxLength);
}
final PdfStream buf = PdfStream();
final PdfGraphics g = PdfGraphics(page, buf);
g.setFillColor(textColor);
g.setFont(font, fontSize);
params['/DA'] = PdfSecString.fromStream(object, buf);
if (value != null) {
params['/V'] = PdfSecString.fromString(object, value);
}
if (defaultValue != null) {
params['/DV'] = PdfSecString.fromString(object, defaultValue);
}
if (textAlign != null) {
params['/Q'] = PdfNum(textAlign.index);
}
}
}
class PdfButtonField extends PdfFormField {
PdfButtonField({
@required PdfRect rect,
String fieldName,
String alternateName,
String mappingName,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
PdfColor backgroundColor,
PdfAnnotHighlighting highlighting,
Set<PdfFieldFlags> fieldFlags,
this.value,
this.defaultValue,
}) : super(
rect: rect,
fieldType: '/Btn',
fieldName: fieldName,
border: border,
flags: flags,
date: date,
color: color,
backgroundColor: backgroundColor,
highlighting: highlighting,
alternateName: alternateName,
mappingName: mappingName,
fieldFlags: fieldFlags,
);
final bool value;
final bool defaultValue;
@override
void build(PdfPage page, PdfObject object, PdfDict params) {
super.build(page, object, params);
if (value != null) {
params['/V'] = value ? const PdfName('/Yes') : const PdfName('/Off');
}
if (defaultValue != null) {
params['/DV'] =
defaultValue ? const PdfName('/Yes') : const PdfName('/Off');
}
}
}
... ...
... ... @@ -98,6 +98,11 @@ class PdfString extends PdfDataType {
return PdfString(_string(value), PdfStringFormat.litteral);
}
factory PdfString.fromStream(PdfObject object, PdfStream value,
[PdfStringFormat format = PdfStringFormat.litteral]) {
return PdfString(value.output(), format);
}
factory PdfString.fromDate(DateTime date) {
return PdfString(_date(date));
}
... ... @@ -203,11 +208,28 @@ class PdfSecString extends PdfString {
: super(value, format);
factory PdfSecString.fromString(PdfObject object, String value) {
return PdfSecString(object, PdfString._string(value));
return PdfSecString(
object,
PdfString._string(value),
PdfStringFormat.litteral,
);
}
factory PdfSecString.fromStream(PdfObject object, PdfStream value,
[PdfStringFormat format = PdfStringFormat.litteral]) {
return PdfSecString(
object,
value.output(),
PdfStringFormat.litteral,
);
}
factory PdfSecString.fromDate(PdfObject object, DateTime date) {
return PdfSecString(object, PdfString._date(date));
return PdfSecString(
object,
PdfString._date(date),
PdfStringFormat.litteral,
);
}
final PdfObject object;
... ... @@ -340,3 +362,28 @@ class PdfDict extends PdfDataType {
return values.containsKey(key);
}
}
class PdfColorType extends PdfDataType {
const PdfColorType(this.color);
final PdfColor color;
@override
void output(PdfStream s) {
if (color is PdfColorCmyk) {
final PdfColorCmyk k = color;
PdfArray.fromNum(<double>[
k.cyan,
k.magenta,
k.yellow,
k.black,
]).output(s);
} else {
PdfArray.fromNum(<double>[
color.red,
color.green,
color.blue,
]).output(s);
}
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ignore_for_file: omit_local_variable_types
part of pdf;
mixin PdfGraphicStream on PdfObject {
/// Isolated transparency: If this flag is true, objects within the group
/// shall be composited against a fully transparent initial backdrop;
/// if false, they shall be composited against the group’s backdrop
bool isolatedTransparency = false;
/// Whether the transparency group is a knockout group.
/// If this flag is false, later objects within the group shall be composited
/// with earlier ones with which they overlap; if true, they shall be
/// composited with the group’s initial backdrop and shall overwrite any
/// earlier overlapping objects.
bool knockoutTransparency = false;
/// The fonts associated with this page
final Map<String, PdfFont> fonts = <String, PdfFont>{};
/// The fonts associated with this page
final Map<String, PdfShading> shading = <String, PdfShading>{};
/// The xobjects or other images in the pdf
final Map<String, PdfXObject> xObjects = <String, PdfXObject>{};
void addFont(PdfFont font) {
if (!fonts.containsKey(font.name)) {
fonts[font.name] = font;
}
}
void addShader(PdfShading shader) {
if (!shading.containsKey(shader.name)) {
shading[shader.name] = shader;
}
}
void addXObject(PdfXObject object) {
if (!xObjects.containsKey(object.name)) {
xObjects[object.name] = object;
}
}
PdfFont getDefaultFont() {
if (pdfDocument.fonts.isEmpty) {
PdfFont.helvetica(pdfDocument);
}
return pdfDocument.fonts.elementAt(0);
}
String stateName(PdfGraphicState state) {
return pdfDocument.graphicStates.stateName(state);
}
@override
void _prepare() {
super._prepare();
// This holds any resources for this page
final PdfDict resources = PdfDict();
resources['/ProcSet'] = PdfArray(const <PdfName>[
PdfName('/PDF'),
PdfName('/Text'),
PdfName('/ImageB'),
PdfName('/ImageC'),
]);
// fonts
if (fonts.isNotEmpty) {
resources['/Font'] = PdfDict.fromObjectMap(fonts);
}
// shading
if (shading.isNotEmpty) {
resources['/Shading'] = PdfDict.fromObjectMap(shading);
}
// Now the XObjects
if (xObjects.isNotEmpty) {
resources['/XObject'] = PdfDict.fromObjectMap(xObjects);
}
if (pdfDocument.hasGraphicStates) {
// Declare Transparency Group settings
params['/Group'] = PdfDict(<String, PdfDataType>{
'/Type': const PdfName('/Group'),
'/S': const PdfName('/Transparency'),
'/CS': const PdfName('/DeviceRGB'),
'/I': PdfBool(isolatedTransparency),
'/K': PdfBool(knockoutTransparency),
});
resources['/ExtGState'] = pdfDocument.graphicStates.ref();
}
params['/Resources'] = resources;
}
}
class PdfGraphicXObject extends PdfXObject with PdfGraphicStream {
PdfGraphicXObject(
PdfDocument pdfDocument, [
String subtype,
]) : super(pdfDocument, subtype);
}
... ...
... ... @@ -55,7 +55,7 @@ class _PdfGraphicsContext {
}
class PdfGraphics {
PdfGraphics(this.page, this.buf) {
PdfGraphics(this._page, this.buf) {
_context = _PdfGraphicsContext(ctm: Matrix4.identity());
}
... ... @@ -66,17 +66,11 @@ class PdfGraphics {
_PdfGraphicsContext _context;
final Queue<_PdfGraphicsContext> _contextQueue = Queue<_PdfGraphicsContext>();
final PdfPage page;
final PdfGraphicStream _page;
final PdfStream buf;
PdfFont get defaultFont {
if (page.pdfDocument.fonts.isEmpty) {
PdfFont.helvetica(page.pdfDocument);
}
return page.pdfDocument.fonts.elementAt(0);
}
PdfFont get defaultFont => _page.getDefaultFont();
void fillPath() {
buf.putString('f\n');
... ... @@ -97,7 +91,7 @@ class PdfGraphics {
/// Apply a shader
void applyShader(PdfShading shader) {
// The shader needs to be registered in the page resources
page.shading[shader.name] = shader;
_page.addShader(shader);
buf.putString('${shader.name} sh\n');
}
... ... @@ -139,7 +133,7 @@ class PdfGraphics {
h ??= img.height.toDouble() * w / img.width.toDouble();
// The image needs to be registered in the page resources
page.xObjects[img.name] = img;
_page.addXObject(img);
// q w 0 0 h x y cm % the coordinate matrix
buf.putString('q ');
... ... @@ -232,51 +226,68 @@ class PdfGraphics {
lineTo(x, y + rv);
}
/// This draws a string.
///
/// @param x coordinate
/// @param y coordinate
/// @param s String to draw
void drawString(
/// Set the current font and size
void setFont(
PdfFont font,
double size,
String s,
double x,
double y, {
double size, {
double charSpace = 0,
double wordSpace = 0,
double scale = 1,
PdfTextRenderingMode mode = PdfTextRenderingMode.fill,
double rise = 0,
}) {
if (!page.fonts.containsKey(font.name)) {
page.fonts[font.name] = font;
}
buf.putString('BT ');
PdfNumList(<double>[x, y]).output(buf);
buf.putString(' Td ${font.name} ');
buf.putString('${font.name} ');
PdfNum(size).output(buf);
buf.putString(' Tf ');
buf.putString(' Tf\n');
if (charSpace != 0) {
PdfNum(charSpace).output(buf);
buf.putString(' Tc ');
buf.putString(' Tc\n');
}
if (wordSpace != 0) {
PdfNum(wordSpace).output(buf);
buf.putString(' Tw ');
buf.putString(' Tw\n');
}
if (scale != 1) {
PdfNum(scale * 100).output(buf);
buf.putString(' Tz ');
buf.putString(' Tz\n');
}
if (rise != 0) {
PdfNum(rise).output(buf);
buf.putString(' Ts ');
buf.putString(' Ts\n');
}
if (mode != PdfTextRenderingMode.fill) {
buf.putString('${mode.index} Tr ');
buf.putString('${mode.index} Tr\n');
}
}
/// This draws a string.
///
/// @param x coordinate
/// @param y coordinate
/// @param s String to draw
void drawString(
PdfFont font,
double size,
String s,
double x,
double y, {
double charSpace = 0,
double wordSpace = 0,
double scale = 1,
PdfTextRenderingMode mode = PdfTextRenderingMode.fill,
double rise = 0,
}) {
_page.addFont(font);
buf.putString('BT ');
PdfNumList(<double>[x, y]).output(buf);
buf.putString(' Td ');
setFont(font, size,
charSpace: charSpace,
mode: mode,
rise: rise,
scale: scale,
wordSpace: wordSpace);
buf.putString('[');
font.putText(buf, s);
buf.putString(']TJ ET\n');
... ... @@ -320,7 +331,7 @@ class PdfGraphics {
/// Set the graphic state for drawing
void setGraphicState(PdfGraphicState state) {
final String name = page.pdfDocument.graphicStates.stateName(state);
final String name = _page.stateName(state);
buf.putString('$name gs\n');
}
... ...
... ... @@ -199,5 +199,6 @@ class PdfImage extends PdfXObject {
final PdfImageOrientation orientation;
/// Name of the image
@override
String get name => '/I$objser';
}
... ...
... ... @@ -18,7 +18,7 @@
part of pdf;
class PdfPage extends PdfObject {
class PdfPage extends PdfObject with PdfGraphicStream {
/// This constructs a Page object, which will hold any contents for this
/// page.
///
... ... @@ -41,30 +41,9 @@ class PdfPage extends PdfObject {
/// -1 indicates no thumbnail.
PdfObject thumbnail;
/// Isolated transparency: If this flag is true, objects within the group
/// shall be composited against a fully transparent initial backdrop;
/// if false, they shall be composited against the group’s backdrop
bool isolatedTransparency = false;
/// Whether the transparency group is a knockout group.
/// If this flag is false, later objects within the group shall be composited
/// with earlier ones with which they overlap; if true, they shall be
/// composited with the group’s initial backdrop and shall overwrite any
/// earlier overlapping objects.
bool knockoutTransparency = false;
/// This holds any Annotations contained within this page.
List<PdfAnnot> annotations = <PdfAnnot>[];
/// The fonts associated with this page
final Map<String, PdfFont> fonts = <String, PdfFont>{};
/// The fonts associated with this page
final Map<String, PdfShading> shading = <String, PdfShading>{};
/// The xobjects or other images in the pdf
final Map<String, PdfXObject> xObjects = <String, PdfXObject>{};
/// This returns a [PdfGraphics] object, which can then be used to render
/// on to this page. If a previous [PdfGraphics] object was used, this object
/// is appended to the page, and will be drawn over the top of any previous
... ... @@ -88,7 +67,6 @@ class PdfPage extends PdfObject {
annotations.add(ob);
}
/// @param os OutputStream to send the object to
@override
void _prepare() {
super._prepare();
... ... @@ -116,47 +94,6 @@ class PdfPage extends PdfObject {
}
}
// Now the resources
/// This holds any resources for this page
final PdfDict resources = PdfDict();
resources['/ProcSet'] = PdfArray(const <PdfName>[
PdfName('/PDF'),
PdfName('/Text'),
PdfName('/ImageB'),
PdfName('/ImageC'),
]);
// fonts
if (fonts.isNotEmpty) {
resources['/Font'] = PdfDict.fromObjectMap(fonts);
}
// shading
if (shading.isNotEmpty) {
resources['/Shading'] = PdfDict.fromObjectMap(shading);
}
// Now the XObjects
if (xObjects.isNotEmpty) {
resources['/XObject'] = PdfDict.fromObjectMap(xObjects);
}
if (pdfDocument.hasGraphicStates) {
// Declare Transparency Group settings
params['/Group'] = PdfDict(<String, PdfDataType>{
'/Type': const PdfName('/Group'),
'/S': const PdfName('/Transparency'),
'/CS': const PdfName('/DeviceRGB'),
'/I': PdfBool(isolatedTransparency),
'/K': PdfBool(knockoutTransparency),
});
resources['/ExtGState'] = pdfDocument.graphicStates.ref();
}
params['/Resources'] = resources;
// The thumbnail
if (thumbnail != null) {
params['/Thumb'] = thumbnail.ref();
... ...
... ... @@ -21,4 +21,6 @@ class PdfXObject extends PdfObjectStream {
: super(pdfDocument, type: '/XObject', isBinary: isBinary) {
params['/Subtype'] = PdfName(subtype);
}
String get name => 'X$objser';
}
... ...
... ... @@ -45,6 +45,7 @@ part 'widgets/decoration.dart';
part 'widgets/document.dart';
part 'widgets/flex.dart';
part 'widgets/font.dart';
part 'widgets/forms.dart';
part 'widgets/geometry.dart';
part 'widgets/grid_view.dart';
part 'widgets/image.dart';
... ...
... ... @@ -45,13 +45,6 @@ class Anchor extends SingleChildWidget {
}
abstract class AnnotationBuilder {
PdfRect localToGlobal(Context context, PdfRect box) {
final Matrix4 mat = context.canvas.getTransform();
final Vector3 lt = mat.transform3(Vector3(box.left, box.bottom, 0));
final Vector3 rb = mat.transform3(Vector3(box.right, box.top, 0));
return PdfRect.fromLTRB(lt.x, lt.y, rb.x, rb.y);
}
void build(Context context, PdfRect box);
}
... ... @@ -65,7 +58,7 @@ class AnnotationLink extends AnnotationBuilder {
PdfAnnot(
context.page,
PdfAnnotNamedLink(
rect: localToGlobal(context, box),
rect: context.localToGlobal(box),
dest: destination,
),
);
... ... @@ -82,7 +75,7 @@ class AnnotationUrl extends AnnotationBuilder {
PdfAnnot(
context.page,
PdfAnnotUrlLink(
rect: localToGlobal(context, box),
rect: context.localToGlobal(box),
url: destination,
),
);
... ... @@ -128,7 +121,7 @@ class AnnotationSignature extends AnnotationBuilder {
PdfAnnot(
context.page,
PdfAnnotSign(
localToGlobal(context, box),
rect: context.localToGlobal(box),
fieldName: name,
border: border,
flags: flags,
... ... @@ -140,6 +133,82 @@ class AnnotationSignature extends AnnotationBuilder {
}
}
class AnnotationTextField extends AnnotationBuilder {
AnnotationTextField({
this.name,
this.border,
this.flags,
this.date,
this.color,
this.backgroundColor,
this.highlighting,
this.maxLength,
this.alternateName,
this.mappingName,
this.fieldFlags,
this.value,
this.defaultValue,
this.textStyle,
});
final String name;
final PdfBorder border;
final Set<PdfAnnotFlags> flags;
final DateTime date;
final PdfColor color;
final PdfColor backgroundColor;
final PdfAnnotHighlighting highlighting;
final int maxLength;
final String value;
final String defaultValue;
final TextStyle textStyle;
final String alternateName;
final String mappingName;
final Set<PdfFieldFlags> fieldFlags;
@override
void build(Context context, PdfRect box) {
final TextStyle _textStyle =
Theme.of(context).defaultTextStyle.merge(textStyle);
PdfAnnot(
context.page,
PdfTextField(
rect: context.localToGlobal(box),
fieldName: name,
border: border,
flags: flags,
date: date,
color: color,
backgroundColor: backgroundColor,
highlighting: highlighting,
maxLength: maxLength,
alternateName: alternateName,
mappingName: mappingName,
fieldFlags: fieldFlags,
value: value,
defaultValue: defaultValue,
font: _textStyle.font.getFont(context),
fontSize: _textStyle.fontSize,
textColor: _textStyle.color,
),
);
}
}
class Annotation extends SingleChildWidget {
Annotation({Widget child, this.builder}) : super(child: child);
... ... @@ -199,3 +268,42 @@ class Signature extends Annotation {
highlighting: highlighting,
));
}
class TextField extends Annotation {
TextField({
Widget child,
double width = 120,
double height = 13,
String name,
PdfBorder border,
Set<PdfAnnotFlags> flags,
DateTime date,
PdfColor color,
PdfColor backgroundColor,
PdfAnnotHighlighting highlighting,
int maxLength,
String alternateName,
String mappingName,
Set<PdfFieldFlags> fieldFlags,
String value,
String defaultValue,
TextStyle textStyle,
}) : super(
child: child ?? SizedBox(width: width, height: height),
builder: AnnotationTextField(
name: name,
border: border,
flags: flags,
date: date,
color: color,
backgroundColor: backgroundColor,
highlighting: highlighting,
maxLength: maxLength,
alternateName: alternateName,
mappingName: mappingName,
fieldFlags: fieldFlags,
value: value,
defaultValue: defaultValue,
textStyle: textStyle,
));
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ignore_for_file: omit_local_variable_types
part of widget;
class Checkbox extends SingleChildWidget {
Checkbox({
@required this.value,
this.defaultValue,
this.tristate = false,
this.activeColor = PdfColors.blue,
this.checkColor = PdfColors.white,
@required this.name,
double width = 13,
double height = 13,
BoxDecoration decoration,
}) : super(
child: Container(
width: width,
height: height,
margin: const EdgeInsets.all(1),
decoration: decoration ??
const BoxDecoration(
border: BoxBorder(
left: true,
top: true,
bottom: true,
right: true,
color: PdfColors.grey600,
width: 2,
))));
final bool value;
final bool defaultValue;
final bool tristate;
final PdfColor activeColor;
final PdfColor checkColor;
final String name;
@override
void paint(Context context) {
super.paint(context);
paintChild(context);
final PdfButtonField bf = PdfButtonField(
rect: context.localToGlobal(box),
fieldName: name,
value: value,
defaultValue: value,
flags: <PdfAnnotFlags>{PdfAnnotFlags.print},
);
final PdfGraphics g =
bf.appearance(context.document, PdfAnnotApparence.normal, name: '/Yes');
g.drawRect(0, 0, bf.rect.width, bf.rect.height);
g.setFillColor(activeColor);
g.fillPath();
g.moveTo(2, bf.rect.height / 2);
g.lineTo(bf.rect.width / 3, bf.rect.height / 4);
g.lineTo(bf.rect.width - 2, bf.rect.height / 4 * 3);
g.setStrokeColor(checkColor);
g.setLineWidth(2);
g.strokePath();
bf.appearance(context.document, PdfAnnotApparence.normal, name: '/Off');
PdfAnnot(context.page, bf);
}
}
class FlatButton extends SingleChildWidget {
FlatButton({
PdfColor textColor = PdfColors.white,
PdfColor color = PdfColors.blue,
PdfColor colorDown = PdfColors.red,
PdfColor colorRollover = PdfColors.blueAccent,
EdgeInsets padding,
BoxDecoration decoration,
Widget child,
@required this.name,
}) : _childDown = Container(
child: DefaultTextStyle(
style: TextStyle(color: textColor),
child: child,
),
decoration: decoration ??
BoxDecoration(
color: colorDown,
borderRadiusEx: const BorderRadius.all(Radius.circular(2)),
),
padding: padding ??
const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
),
_childRollover = Container(
child: DefaultTextStyle(
style: TextStyle(color: textColor),
child: child,
),
decoration: decoration ??
BoxDecoration(
color: colorRollover,
borderRadiusEx: const BorderRadius.all(Radius.circular(2)),
),
padding: padding ??
const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
),
super(
child: Container(
child: DefaultTextStyle(
style: TextStyle(color: textColor),
child: child,
),
decoration: decoration ??
BoxDecoration(
color: color,
borderRadiusEx: const BorderRadius.all(Radius.circular(2)),
),
padding: padding ??
const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
),
);
// final PdfColor textColor;
// final PdfColor color;
// final EdgeInsets padding;
final String name;
final Widget _childDown;
final Widget _childRollover;
@override
void paint(Context context) {
super.paint(context);
final PdfButtonField bf = PdfButtonField(
rect: context.localToGlobal(box),
fieldName: name,
flags: <PdfAnnotFlags>{PdfAnnotFlags.print},
fieldFlags: <PdfFieldFlags>{PdfFieldFlags.pushButton},
);
final Matrix4 mat = context.canvas.getTransform();
final Vector3 translation = Vector3(0, 0, 0);
final Quaternion rotation = Quaternion(0, 0, 0, 0);
final Vector3 scale = Vector3(0, 0, 0);
mat
..decompose(translation, rotation, scale)
..leftTranslate(-translation.x, -translation.y)
..translate(box.x, box.y);
final Context cn = context.copyWith(
canvas: bf.appearance(context.document, PdfAnnotApparence.normal,
matrix: mat, boundingBox: box));
child.layout(
cn, BoxConstraints.tightFor(width: box.width, height: box.height));
child.paint(cn);
final Context cd = context.copyWith(
canvas: bf.appearance(context.document, PdfAnnotApparence.down,
matrix: mat, boundingBox: box));
_childDown.layout(
cd, BoxConstraints.tightFor(width: box.width, height: box.height));
_childDown.paint(cd);
final Context cr = context.copyWith(
canvas: bf.appearance(context.document, PdfAnnotApparence.rollover,
matrix: mat, boundingBox: box));
_childRollover.layout(
cr, BoxConstraints.tightFor(width: box.width, height: box.height));
_childRollover.paint(cr);
PdfAnnot(context.page, bf);
}
}
... ...
... ... @@ -79,6 +79,22 @@ class Context {
}
return copyWith(inherited: inherited);
}
PdfRect localToGlobal(PdfRect box) {
final Matrix4 mat = canvas.getTransform();
final Vector3 lt = mat.transform3(Vector3(box.left, box.bottom, 0));
final Vector3 lb = mat.transform3(Vector3(box.left, box.top, 0));
final Vector3 rt = mat.transform3(Vector3(box.right, box.bottom, 0));
final Vector3 rb = mat.transform3(Vector3(box.right, box.top, 0));
final List<double> x = <double>[lt.x, lb.x, rt.x, rb.x];
final List<double> y = <double>[lt.y, lb.y, rt.y, rb.y];
return PdfRect.fromLTRB(
x.reduce(math.min),
y.reduce(math.min),
x.reduce(math.max),
y.reduce(math.max),
);
}
}
class Inherited {
... ...
... ... @@ -36,6 +36,7 @@ import 'widget_chart_test.dart' as widget_chart;
import 'widget_clip_test.dart' as widget_clip;
import 'widget_container_test.dart' as widget_container;
import 'widget_flex_test.dart' as widget_flex;
import 'widget_form_test.dart' as widget_form;
import 'widget_grid_view_test.dart' as widget_grid_view;
import 'widget_multipage_test.dart' as widget_multipage;
import 'widget_opacity_test.dart' as widget_opacity;
... ... @@ -68,6 +69,7 @@ void main() {
widget_clip.main();
widget_container.main();
widget_flex.main();
widget_form.main();
widget_grid_view.main();
widget_multipage.main();
widget_opacity.main();
... ...
... ... @@ -35,17 +35,17 @@ void main() {
PdfAnnot(
page,
const PdfAnnotText(
rect: PdfRect(100, 100, 50, 50),
PdfAnnotText(
rect: const PdfRect(100, 100, 50, 50),
content: 'Hello',
),
);
PdfAnnot(
page,
const PdfAnnotNamedLink(
PdfAnnotNamedLink(
dest: 'target',
rect: PdfRect(100, 150, 50, 50),
rect: const PdfRect(100, 150, 50, 50),
),
);
g.drawRect(100, 150, 50, 50);
... ... @@ -53,14 +53,27 @@ void main() {
PdfAnnot(
page,
const PdfAnnotUrlLink(
rect: PdfRect(100, 250, 50, 50),
PdfAnnotUrlLink(
rect: const PdfRect(100, 250, 50, 50),
url: 'https://github.com/DavBfr/dart_pdf/',
),
);
g.drawRect(100, 250, 50, 50);
g.strokePath();
PdfAnnot(
page,
PdfTextField(
rect: const PdfRect(100, 50, 50, 20),
fieldName: 'test',
font: PdfFont.helvetica(pdf),
fontSize: 10,
textColor: PdfColors.blue,
),
);
// g.drawRect(100, 50, 50, 20);
g.strokePath();
final File file = File('annotations.pdf');
file.writeAsBytesSync(pdf.save());
});
... ...
... ... @@ -22,7 +22,8 @@ import 'dart:typed_data';
import 'package:pdf/pdf.dart';
import 'package:test/test.dart';
void printText(PdfGraphics canvas, String text, PdfFont font, double top) {
void printText(
PdfPage page, PdfGraphics canvas, String text, PdfFont font, double top) {
text = text + font.fontName;
const double fontSize = 20;
final PdfFontMetrics metrics = font.stringMetrics(text) * fontSize;
... ... @@ -30,7 +31,7 @@ void printText(PdfGraphics canvas, String text, PdfFont font, double top) {
const double deb = 5;
const double x = 50;
final double y = canvas.page.pageFormat.height - top;
final double y = page.pageFormat.height - top;
canvas
..drawRect(x + metrics.left, y + metrics.top, metrics.width, metrics.height)
... ... @@ -51,12 +52,13 @@ void printText(PdfGraphics canvas, String text, PdfFont font, double top) {
..drawString(font, fontSize, text, x, y);
}
void printTextTtf(PdfGraphics canvas, String text, File ttfFont, double top) {
void printTextTtf(
PdfPage page, PdfGraphics canvas, String text, File ttfFont, double top) {
final Uint8List fontData = ttfFont.readAsBytesSync();
final PdfTtfFont font =
PdfTtfFont(canvas.page.pdfDocument, fontData.buffer.asByteData());
PdfTtfFont(page.pdfDocument, fontData.buffer.asByteData());
printText(canvas, text, font, top);
printText(page, canvas, text, font, top);
}
void main() {
... ... @@ -69,11 +71,12 @@ void main() {
int top = 0;
const String s = 'Hello Lukáča ';
printTextTtf(g, s, File('open-sans.ttf'), 30.0 + 30.0 * top++);
printTextTtf(g, s, File('open-sans-bold.ttf'), 30.0 + 30.0 * top++);
printTextTtf(g, s, File('roboto.ttf'), 30.0 + 30.0 * top++);
printTextTtf(g, s, File('noto-sans.ttf'), 30.0 + 30.0 * top++);
printTextTtf(g, '你好 檯號 ', File('genyomintw.ttf'), 30.0 + 30.0 * top++);
printTextTtf(page, g, s, File('open-sans.ttf'), 30.0 + 30.0 * top++);
printTextTtf(page, g, s, File('open-sans-bold.ttf'), 30.0 + 30.0 * top++);
printTextTtf(page, g, s, File('roboto.ttf'), 30.0 + 30.0 * top++);
printTextTtf(page, g, s, File('noto-sans.ttf'), 30.0 + 30.0 * top++);
printTextTtf(
page, g, '你好 檯號 ', File('genyomintw.ttf'), 30.0 + 30.0 * top++);
final File file = File('ttf.pdf');
file.writeAsBytesSync(pdf.save());
... ...
... ... @@ -21,7 +21,8 @@ import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:test/test.dart';
void printText(PdfGraphics canvas, String text, PdfFont font, double top) {
void printText(
PdfPage page, PdfGraphics canvas, String text, PdfFont font, double top) {
text = text + font.fontName;
const double fontSize = 20;
final PdfFontMetrics metrics = font.stringMetrics(text) * fontSize;
... ... @@ -29,7 +30,7 @@ void printText(PdfGraphics canvas, String text, PdfFont font, double top) {
const double deb = 5;
const double x = 50;
final double y = canvas.page.pageFormat.height - top;
final double y = page.pageFormat.height - top;
canvas
..drawRect(x + metrics.left, y + metrics.top, metrics.width, metrics.height)
... ... @@ -60,23 +61,24 @@ void main() {
int top = 0;
const String s = 'Hello ';
printText(g, s, PdfFont.courier(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.courierBold(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.courierOblique(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.courierBoldOblique(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.courier(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.courierBold(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.courierOblique(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.courierBoldOblique(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.helvetica(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.helveticaBold(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.helveticaOblique(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.helveticaBoldOblique(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.helvetica(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.helveticaBold(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.helveticaOblique(pdf), 20.0 + 30.0 * top++);
printText(
page, g, s, PdfFont.helveticaBoldOblique(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.times(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.timesBold(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.timesItalic(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.timesBoldItalic(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.times(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.timesBold(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.timesItalic(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.timesBoldItalic(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.symbol(pdf), 20.0 + 30.0 * top++);
printText(g, s, PdfFont.zapfDingbats(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.symbol(pdf), 20.0 + 30.0 * top++);
printText(page, g, s, PdfFont.zapfDingbats(pdf), 20.0 + 30.0 * top++);
final File file = File('type1.pdf');
file.writeAsBytesSync(pdf.save());
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ignore_for_file: omit_local_variable_types
import 'dart:io';
import 'package:test/test.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
Document pdf;
class Label extends StatelessWidget {
Label({this.label, this.width});
final String label;
final double width;
@override
Widget build(Context context) {
return Container(
child: Text(label),
width: width,
alignment: Alignment.centerRight,
margin: const EdgeInsets.only(right: 5),
);
}
}
class Decorated extends StatelessWidget {
Decorated({this.child, this.color});
final Widget child;
final PdfColor color;
@override
Widget build(Context context) {
return Container(
child: child,
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: color ?? PdfColors.yellow100,
border: const BoxBorder(
left: true,
top: true,
right: true,
bottom: true,
color: PdfColors.grey,
width: .5,
),
),
);
}
}
void main() {
setUpAll(() {
// Document.debug = true;
pdf = Document();
});
test(
'Form',
() {
pdf.addPage(
Page(
build: (Context context) => Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
Label(label: 'Given Name:', width: 100),
Decorated(
child: TextField(
name: 'Given Name',
textStyle: const TextStyle(color: PdfColors.amber),
)),
//
SizedBox(width: double.infinity, height: 10),
//
Label(label: 'Family Name:', width: 100),
Decorated(child: TextField(name: 'Family Name')),
//
SizedBox(width: double.infinity, height: 10),
//
Label(label: 'Address:', width: 100),
Decorated(child: TextField(name: 'Address')),
//
SizedBox(width: double.infinity, height: 10),
//
Label(label: 'Postcode:', width: 100),
Decorated(
child: TextField(name: 'Postcode', width: 60, maxLength: 6)),
//
Label(label: 'City:', width: 30),
Decorated(child: TextField(name: 'City')),
//
SizedBox(width: double.infinity, height: 10),
//
Label(label: 'Country:', width: 100),
Decorated(
child: TextField(
name: 'Country',
color: PdfColors.blue,
)),
//
SizedBox(width: double.infinity, height: 10),
//
Label(label: 'Checkbox:', width: 100),
Checkbox(
name: 'Checkbox',
value: true,
defaultValue: true,
),
//
SizedBox(width: double.infinity, height: 10),
//
Transform.rotateBox(
angle: .7,
child: FlatButton(
name: 'submit',
child: Text('Submit'),
),
)
],
),
),
);
},
);
tearDownAll(() {
final File file = File('widgets-form.pdf');
file.writeAsBytesSync(pdf.save());
});
}
... ...
No preview for this file type