David PHAM-VAN

Add debugging information

... ... @@ -8,6 +8,7 @@
- Implement fallback font
- Implement Emoji support
- Improve outlines containing non-sequential level increments [Roel Spilker]
- Add debugging information
## 3.6.5
... ...
... ... @@ -16,6 +16,7 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:meta/meta.dart';
... ... @@ -25,10 +26,12 @@ import 'color.dart';
import 'obj/object.dart';
import 'stream.dart';
const _kIndentSize = 2;
abstract class PdfDataType {
const PdfDataType();
void output(PdfStream s);
void output(PdfStream s, [int? indent]);
PdfStream _toStream() {
final s = PdfStream();
... ... @@ -53,7 +56,7 @@ class PdfBool extends PdfDataType {
final bool value;
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
s.putString(value ? 'true' : 'false');
}
... ... @@ -81,7 +84,7 @@ class PdfNum extends PdfDataType {
final num value;
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
if (value is int) {
s.putString(value.toInt().toString());
} else {
... ... @@ -119,12 +122,12 @@ class PdfNumList extends PdfDataType {
final List<num> values;
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
for (var n = 0; n < values.length; n++) {
if (n > 0) {
s.putByte(0x20);
}
PdfNum(values[n]).output(s);
PdfNum(values[n]).output(s, indent);
}
}
... ... @@ -289,7 +292,7 @@ class PdfString extends PdfDataType {
}
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
_output(s, value);
}
... ... @@ -346,9 +349,9 @@ class PdfSecString extends PdfString {
final PdfObject object;
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
if (object.pdfDocument.encryption == null) {
return super.output(s);
return super.output(s, indent);
}
final enc = object.pdfDocument.encryption!.encrypt(value, object);
... ... @@ -362,7 +365,7 @@ class PdfName extends PdfDataType {
final String value;
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
assert(value[0] == '/');
final bytes = <int>[];
for (final c in value.codeUnits) {
... ... @@ -404,7 +407,7 @@ class PdfNull extends PdfDataType {
const PdfNull();
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
s.putString('null');
}
... ... @@ -425,7 +428,7 @@ class PdfIndirect extends PdfDataType {
final int gen;
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
s.putString('$ser $gen R');
}
... ... @@ -465,21 +468,39 @@ class PdfArray<T extends PdfDataType> extends PdfDataType {
}
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
if (indent != null) {
s.putBytes(List<int>.filled(indent, 0x20));
indent += _kIndentSize;
}
s.putString('[');
if (values.isNotEmpty) {
for (var n = 0; n < values.length; n++) {
final val = values[n];
if (n > 0 &&
!(val is PdfName ||
val is PdfString ||
val is PdfArray ||
val is PdfDict)) {
s.putByte(0x20);
if (indent != null) {
s.putByte(0x0a);
if (val is! PdfDict && val is! PdfArray) {
s.putBytes(List<int>.filled(indent, 0x20));
}
} else {
if (n > 0 &&
!(val is PdfName ||
val is PdfString ||
val is PdfArray ||
val is PdfDict)) {
s.putByte(0x20);
}
}
val.output(s);
val.output(s, indent);
}
if (indent != null) {
s.putByte(0x0a);
}
}
if (indent != null) {
indent -= _kIndentSize;
s.putBytes(List<int>.filled(indent, 0x20));
}
s.putString(']');
}
... ... @@ -544,15 +565,44 @@ class PdfDict<T extends PdfDataType> extends PdfDataType {
}
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
if (indent != null) {
s.putBytes(List<int>.filled(indent, 0x20));
}
s.putBytes(const <int>[0x3c, 0x3c]);
var len = 0;
var n = 1;
if (indent != null) {
s.putByte(0x0a);
indent += _kIndentSize;
len = values.keys.fold<int>(0, (p, e) => math.max(p, e.length));
}
values.forEach((String k, T v) {
if (indent != null) {
s.putBytes(List<int>.filled(indent, 0x20));
n = len - k.length + 1;
}
s.putString(k);
if (v is PdfNum || v is PdfBool || v is PdfNull || v is PdfIndirect) {
s.putByte(0x20);
if (indent != null) {
if (v is PdfDict || v is PdfArray) {
s.putByte(0x0a);
} else {
s.putBytes(List<int>.filled(n, 0x20));
}
} else {
if (v is PdfNum || v is PdfBool || v is PdfNull || v is PdfIndirect) {
s.putByte(0x20);
}
}
v.output(s, indent);
if (indent != null) {
s.putByte(0x0a);
}
v.output(s);
});
if (indent != null) {
indent -= _kIndentSize;
s.putBytes(List<int>.filled(indent, 0x20));
}
s.putBytes(const <int>[0x3e, 0x3e]);
}
... ... @@ -633,7 +683,7 @@ class PdfDictStream extends PdfDict<PdfDataType> {
final bool compress;
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
final _values = PdfDict(values);
Uint8List? _data;
... ... @@ -641,7 +691,7 @@ class PdfDictStream extends PdfDict<PdfDataType> {
if (_values.containsKey('/Filter')) {
// The data is already in the right format
_data = data;
} else if (compress && object.pdfDocument.deflate != null) {
} else if (compress && object.pdfDocument.compress) {
// Compress the data
final newData = Uint8List.fromList(object.pdfDocument.deflate!(data));
if (newData.lengthInBytes < data.lengthInBytes) {
... ... @@ -668,7 +718,10 @@ class PdfDictStream extends PdfDict<PdfDataType> {
_values['/Length'] = PdfNum(_data.length);
_values.output(s);
_values.output(s, indent);
if (indent != null) {
s.putByte(0x0a);
}
s.putString('stream\n');
s.putBytes(_data);
s.putString('\nendstream\n');
... ... @@ -681,7 +734,7 @@ class PdfColorType extends PdfDataType {
final PdfColor color;
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
if (color is PdfColorCmyk) {
final k = color as PdfColorCmyk;
PdfArray.fromNum(<double>[
... ... @@ -689,13 +742,13 @@ class PdfColorType extends PdfDataType {
k.magenta,
k.yellow,
k.black,
]).output(s);
]).output(s, indent);
} else {
PdfArray.fromNum(<double>[
color.red,
color.green,
color.blue,
]).output(s);
]).output(s, indent);
}
}
... ...
... ... @@ -161,6 +161,8 @@ class PdfDocument {
Uint8List? _documentID;
bool get compress => deflate != null;
/// Generates the document ID
Uint8List get documentID {
if (_documentID == null) {
... ... @@ -203,7 +205,7 @@ class PdfDocument {
/// This writes the document to an OutputStream.
Future<void> _write(PdfStream os) async {
final pos = PdfOutput(os, version);
final pos = PdfOutput(os, version, compress);
// Write each object to the [PdfStream]. We call via the output
// as that builds the xref table
... ...
... ... @@ -109,6 +109,10 @@ class PdfGraphicState {
/// Color transfer function
final PdfFunction? transferFunction;
@override
String toString() =>
'$runtimeType fillOpacity:$fillOpacity strokeOpacity:$strokeOpacity blendMode:$blendMode softMask:$softMask transferFunction:$transferFunction';
PdfDict output() {
final params = PdfDict();
... ...
... ... @@ -122,33 +122,75 @@ class PdfGraphics {
/// Draw a surface on the previously defined shape
/// set evenOdd to false to use the nonzero winding number rule to determine the region to fill and to true to use the even-odd rule to determine the region to fill
void fillPath({bool evenOdd = false}) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('fillPath evenOdd:$evenOdd');
}
return true;
}());
_buf.putString('f${evenOdd ? '*' : ''}\n');
}
/// Draw the contour of the previously defined shape
void strokePath({bool close = false}) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('strokePath close:$close');
}
return true;
}());
_buf.putString('${close ? 's' : 'S'}\n');
}
/// Close the path with a line
void closePath() {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('closePath');
}
return true;
}());
_buf.putString('h\n');
}
/// Create a clipping surface from the previously defined shape,
/// to prevent any further drawing outside
void clipPath({bool evenOdd = false, bool end = true}) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('clipPath evenOdd:$evenOdd end:$end');
}
return true;
}());
_buf.putString('W${evenOdd ? '*' : ''}${end ? ' n' : ''}\n');
}
/// Draw a surface on the previously defined shape and then draw the contour
/// set evenOdd to false to use the nonzero winding number rule to determine the region to fill and to true to use the even-odd rule to determine the region to fill
void fillAndStrokePath({bool evenOdd = false, bool close = false}) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('fillAndStrokePath evenOdd:$evenOdd close:$close');
}
return true;
}());
_buf.putString('${close ? 'b' : 'B'}${evenOdd ? '*' : ''}\n');
}
/// Apply a shader
void applyShader(PdfShading shader) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('applyShader');
}
return true;
}());
// The shader needs to be registered in the page resources
_page.addShader(shader);
_buf.putString('${shader.name} sh\n');
... ... @@ -160,6 +202,13 @@ class PdfGraphics {
/// When using [PdfPage], you can create another fresh Graphics instance,
/// which will draw over this one.
void restoreContext() {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('restoreContext');
}
return true;
}());
if (_contextQueue.isNotEmpty) {
// restore graphics context
_buf.putString('Q\n');
... ... @@ -169,12 +218,26 @@ class PdfGraphics {
/// Save the graphc context
void saveContext() {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('saveContext');
}
return true;
}());
_buf.putString('q\n');
_contextQueue.addLast(_context.copy());
}
/// Draws an image onto the page.
void drawImage(PdfImage img, double x, double y, [double? w, double? h]) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('drawImage x:$x y:$y');
}
return true;
}());
w ??= img.width.toDouble();
h ??= img.height.toDouble() * w / img.width.toDouble();
... ... @@ -215,6 +278,13 @@ class PdfGraphics {
/// Draws a line between two coordinates.
void drawLine(double x1, double y1, double x2, double y2) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('drawLine x1:$x1 y1:$y1 x2:$x2 y2:$y2');
}
return true;
}());
moveTo(x1, y1);
lineTo(x2, y2);
}
... ... @@ -224,6 +294,13 @@ class PdfGraphics {
/// Use clockwise=false to draw the inside of a donnnut
void drawEllipse(double x, double y, double r1, double r2,
{bool clockwise = true}) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('drawEllipse x:$x y:$y r1:$r1 r2:$r2');
}
return true;
}());
moveTo(x, y - r2);
if (clockwise) {
curveTo(x + _m4 * r1, y - r2, x + r1, y - _m4 * r2, x + r1, y);
... ... @@ -245,6 +322,13 @@ class PdfGraphics {
double w,
double h,
) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('drawRect x:$x y:$y w:$w h:$h');
}
return true;
}());
PdfNumList([x, y, w, h]).output(_buf);
_buf.putString(' re\n');
}
... ... @@ -256,6 +340,13 @@ class PdfGraphics {
/// Draws a Rounded Rectangle
void drawRRect(double x, double y, double w, double h, double rv, double rh) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('drawRRect x:$x y:$y w:$w h:$h rv:$rv rh:$rh');
}
return true;
}());
moveTo(x, y + rv);
curveTo(x, y - _m4 * rv + rv, x - _m4 * rh + rh, y, x + rh, y);
lineTo(x + w - rh, y);
... ... @@ -278,6 +369,13 @@ class PdfGraphics {
PdfTextRenderingMode? mode = PdfTextRenderingMode.fill,
double? rise,
}) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setFont');
}
return true;
}());
_buf.putString('${font.name} ');
PdfNum(size).output(_buf);
_buf.putString(' Tf\n');
... ... @@ -315,6 +413,13 @@ class PdfGraphics {
PdfTextRenderingMode mode = PdfTextRenderingMode.fill,
double rise = 0,
}) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('drawString x:$x y:$y size:$size "$s"');
}
return true;
}());
_page.addFont(font);
_buf.putString('BT ');
... ... @@ -332,6 +437,13 @@ class PdfGraphics {
}
void reset() {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('reset');
}
return true;
}());
_buf.putString('0 Tr\n');
}
... ... @@ -343,6 +455,13 @@ class PdfGraphics {
/// Sets the fill color for drawing
void setFillColor(PdfColor? color) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setFillColor ${color?.toHex()}');
}
return true;
}());
if (color is PdfColorCmyk) {
PdfNumList(<double>[color.cyan, color.magenta, color.yellow, color.black])
.output(_buf);
... ... @@ -355,6 +474,13 @@ class PdfGraphics {
/// Sets the stroke color for drawing
void setStrokeColor(PdfColor? color) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setStrokeColor ${color?.toHex()}');
}
return true;
}());
if (color is PdfColorCmyk) {
PdfNumList(<double>[color.cyan, color.magenta, color.yellow, color.black])
.output(_buf);
... ... @@ -367,6 +493,13 @@ class PdfGraphics {
/// Sets the fill pattern for drawing
void setFillPattern(PdfPattern pattern) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setFillPattern');
}
return true;
}());
// The shader needs to be registered in the page resources
_page.addPattern(pattern);
_buf.putString('/Pattern cs${pattern.name} scn\n');
... ... @@ -374,6 +507,13 @@ class PdfGraphics {
/// Sets the stroke pattern for drawing
void setStrokePattern(PdfPattern pattern) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setStrokePattern');
}
return true;
}());
// The shader needs to be registered in the page resources
_page.addPattern(pattern);
_buf.putString('/Pattern CS${pattern.name} SCN\n');
... ... @@ -381,12 +521,26 @@ class PdfGraphics {
/// Set the graphic state for drawing
void setGraphicState(PdfGraphicState state) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setGraphicState $state');
}
return true;
}());
final name = _page.stateName(state);
_buf.putString('$name gs\n');
}
/// Set the transformation Matrix
void setTransform(Matrix4 t) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setTransform\n$t');
}
return true;
}());
final s = t.storage;
PdfNumList(<double>[s[0], s[1], s[4], s[5], s[12], s[13]]).output(_buf);
_buf.putString(' cm\n');
... ... @@ -400,12 +554,26 @@ class PdfGraphics {
/// This adds a line segment to the current path
void lineTo(double x, double y) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('lineTo x:$x y:$y');
}
return true;
}());
PdfNumList([x, y]).output(_buf);
_buf.putString(' l\n');
}
/// This moves the current drawing point.
void moveTo(double x, double y) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('moveTo x:$x y:$y');
}
return true;
}());
PdfNumList([x, y]).output(_buf);
_buf.putString(' m\n');
}
... ... @@ -415,6 +583,13 @@ class PdfGraphics {
/// and (x2,y2) as the control point at the end of the curve.
void curveTo(
double x1, double y1, double x2, double y2, double x3, double y3) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('curveTo x1:$x1 y1:$y1 x2:$x2 y2:$y2 x3:$x3 y3:$y3');
}
return true;
}());
PdfNumList([x1, y1, x2, y2, x3, y3]).output(_buf);
_buf.putString(' c\n');
}
... ... @@ -576,22 +751,50 @@ class PdfGraphics {
/// Set line starting and ending cap type
void setLineCap(PdfLineCap cap) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setLineCap $cap');
}
return true;
}());
_buf.putString('${cap.index} J\n');
}
/// Set line join type
void setLineJoin(PdfLineJoin join) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setLineJoin $join');
}
return true;
}());
_buf.putString('${join.index} j\n');
}
/// Set line width
void setLineWidth(double width) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setLineWidth $width');
}
return true;
}());
PdfNum(width).output(_buf);
_buf.putString(' w\n');
}
/// Set line joint miter limit, applies if the
void setMiterLimit(double limit) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setMiterLimit $limit');
}
return true;
}());
assert(limit >= 1.0);
PdfNum(limit).output(_buf);
_buf.putString(' M\n');
... ... @@ -602,16 +805,37 @@ class PdfGraphics {
///
/// Example: [2 1] will create a dash pattern with 2 on, 1 off, 2 on, 1 off, ...
void setLineDashPattern([List<num> array = const <num>[], int phase = 0]) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('setLineDashPattern $array phase:$phase');
}
return true;
}());
PdfArray.fromNum(array).output(_buf);
_buf.putString(' $phase d\n');
}
void markContentBegin(PdfName tag) {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('markContentBegin');
}
return true;
}());
tag.output(_buf);
_buf.putString(' BMC\n');
}
void markContentEnd() {
assert(() {
if (!_page.pdfDocument.compress) {
_buf.putComment('markContentEnd');
}
return true;
}());
_buf.putString('EMC\n');
}
}
... ...
... ... @@ -60,11 +60,20 @@ abstract class PdfObject<T extends PdfDataType> {
/// The write method should call this before writing anything to the
/// OutputStream. This will send the standard header for each object.
void _writeStart(PdfStream os) {
assert(() {
if (!pdfDocument.compress) {
os.putComment('');
os.putComment('-' * 78);
os.putComment('$runtimeType');
}
return true;
}());
os.putString('$objser $objgen obj\n');
}
void writeContent(PdfStream os) {
params.output(os);
params.output(os, pdfDocument.compress ? null : 0);
os.putByte(0x0a);
}
... ...
... ... @@ -37,7 +37,7 @@ class PdfObjectDict extends PdfObject<PdfDict> {
@override
void writeContent(PdfStream os) {
if (params.isNotEmpty) {
params.output(os);
params.output(os, pdfDocument.compress ? null : 0);
os.putByte(0x0a);
}
}
... ...
... ... @@ -41,6 +41,6 @@ class PdfObjectStream extends PdfObjectDict {
isBinary: isBinary,
values: params.values,
data: buf.output(),
).output(os);
).output(os, pdfDocument.compress ? null : 0);
}
}
... ...
... ... @@ -62,6 +62,9 @@ class PdfSoftMask {
PdfBaseFunction? _tr;
@override
String toString() => '$runtimeType';
PdfDict output() {
final params = PdfDict({
'/S': const PdfName('/Luminosity'),
... ...
... ... @@ -34,7 +34,7 @@ class PdfUnicodeCmap extends PdfObjectStream {
cmap.fillRange(1, cmap.length, 0x20);
}
buf.putString('/CIDInit/ProcSet findresource begin\n'
buf.putString('/CIDInit/ProcSet\nfindresource begin\n'
'12 dict begin\n'
'begincmap\n'
'/CIDSystemInfo<<\n'
... ...
... ... @@ -27,7 +27,7 @@ import 'xref.dart';
/// PDF document writer
class PdfOutput {
/// This creates a Pdf [PdfStream]
PdfOutput(this.os, this.version) {
PdfOutput(this.os, this.version, bool compress) {
String v;
switch (version) {
case PdfVersion.pdf_1_4:
... ... @@ -40,8 +40,22 @@ class PdfOutput {
os.putString('%PDF-$v\n');
os.putBytes(const <int>[0x25, 0xC2, 0xA5, 0xC2, 0xB1, 0xC3, 0xAB, 0x0A]);
assert(() {
if (!compress) {
_stopwatch = Stopwatch()..start();
os.putComment('');
os.putComment('Verbose dart_pdf');
os.putComment('Creation date: ${DateTime.now()}');
_comment = os.offset;
os.putBytes(List<int>.filled(120, 0x20));
}
return true;
}());
}
late final Stopwatch _stopwatch;
var _comment = 0;
/// Pdf version to output
final PdfVersion version;
... ... @@ -129,9 +143,32 @@ class PdfOutput {
os.putByte(0x0a);
}
assert(() {
if (!rootID!.pdfDocument.compress) {
os.putComment('');
os.putComment('-' * 78);
}
return true;
}());
// the reference to the xref object
os.putString('startxref\n$_xref\n%%EOF\n');
assert(() {
if (!rootID!.pdfDocument.compress) {
_stopwatch.stop();
final h = PdfStream();
h.putComment(
'Creation time: ${_stopwatch.elapsed.inMicroseconds / Duration.microsecondsPerSecond} seconds');
h.putComment('File size: ${os.offset} bytes');
h.putComment('Pages: ${rootID!.pdfDocument.pdfPageList.pages.length}');
h.putComment('Objects: ${xref.offsets.length}');
os.setBytes(_comment, h.output());
}
return true;
}());
if (signatureID != null) {
await signatureID!.writeSignature(os);
}
... ...
... ... @@ -68,4 +68,16 @@ class PdfStream {
}());
putBytes(s!.codeUnits);
}
void putComment(String s) {
if (s.isEmpty) {
putByte(0x0a);
} else {
for (final l in s.split('\n')) {
if (l.isNotEmpty) {
putBytes('% $l\n'.codeUnits);
}
}
}
}
}
... ...
... ... @@ -107,7 +107,7 @@ class PdfXrefTable extends PdfDataType {
}
@override
void output(PdfStream s) {
void output(PdfStream s, [int? indent]) {
s.putString('xref\n');
// Now scan through the offsets list. They should be in sequence.
... ... @@ -153,8 +153,6 @@ class PdfXrefTable extends PdfDataType {
// Sort all references
offsets.sort((a, b) => a.id.compareTo(b.id));
s.putString('$id 0 obj\n');
params['/Type'] = const PdfName('/XRef');
params['/Size'] = PdfNum(id + 1);
... ... @@ -200,12 +198,22 @@ class PdfXrefTable extends PdfDataType {
}
// Write the object
assert(() {
if (!object.pdfDocument.compress) {
s.putComment('');
s.putComment('-' * 78);
s.putComment('$runtimeType $this');
}
return true;
}());
s.putString('$id 0 obj\n');
PdfDictStream(
object: object,
data: o.buffer.asUint8List(),
isBinary: false,
encrypt: false,
values: params.values,
).output(s);
).output(s, object.pdfDocument.compress ? null : 0);
}
}
... ...