diff --git a/pdf/lib/src/pdf/document.dart b/pdf/lib/src/pdf/document.dart index 207a42c9..a2e5cb54 100644 --- a/pdf/lib/src/pdf/document.dart +++ b/pdf/lib/src/pdf/document.dart @@ -14,6 +14,7 @@ * limitations under the License. */ +import 'dart:io'; import 'dart:math' as math; import 'dart:typed_data'; @@ -251,10 +252,22 @@ class PdfDocument { } } + /// Generate the PDF document in a file + Future saveFile(File file) async { + return pdfCompute(() async { + final os = PdfStreamFile(file); + if (prev != null) { + os.putBytes(prev!.bytes); + } + await _write(os); + os.close(); + }); + } + /// Generate the PDF document as a memory file Future save() async { return pdfCompute(() async { - final os = PdfStream(); + final os = PdfStreamBuffer(); if (prev != null) { os.putBytes(prev!.bytes); } diff --git a/pdf/lib/src/pdf/format/base.dart b/pdf/lib/src/pdf/format/base.dart index 2acc9537..287f6a7d 100644 --- a/pdf/lib/src/pdf/format/base.dart +++ b/pdf/lib/src/pdf/format/base.dart @@ -28,8 +28,8 @@ abstract class PdfDataType { void output(PdfObjectBase o, PdfStream s, [int? indent]); - PdfStream _toStream([int? indent]) { - final s = PdfStream(); + PdfStreamBuffer _toStream([int? indent]) { + final s = PdfStreamBuffer(); output( PdfObjectBase( objser: 0, diff --git a/pdf/lib/src/pdf/format/diagnostic.dart b/pdf/lib/src/pdf/format/diagnostic.dart index 231edf12..23f61e1e 100644 --- a/pdf/lib/src/pdf/format/diagnostic.dart +++ b/pdf/lib/src/pdf/format/diagnostic.dart @@ -43,7 +43,7 @@ mixin PdfDiagnostic { void writeDebug(PdfStream os) { assert(() { if (_offset != null) { - final o = PdfStream(); + final o = PdfStreamBuffer(); _properties.forEach(o.putComment); final b = o.output(); os.setBytes( diff --git a/pdf/lib/src/pdf/format/stream.dart b/pdf/lib/src/pdf/format/stream.dart index 8e169bf9..d982bb6d 100644 --- a/pdf/lib/src/pdf/format/stream.dart +++ b/pdf/lib/src/pdf/format/stream.dart @@ -14,9 +14,46 @@ * limitations under the License. */ +import 'dart:io'; import 'dart:typed_data'; -class PdfStream { +abstract class PdfStream { + void putByte(int s); + + void putBytes(List s); + + void setBytes(int offset, Iterable iterable); + + void putStream(PdfStreamBuffer s); + + int get offset; + + void putString(String? s) { + assert(() { + for (final codeUnit in s!.codeUnits) { + if (codeUnit > 0x7f) { + return false; + } + } + return true; + }()); + 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); + } + } + } + } +} + +class PdfStreamBuffer extends PdfStream { static const int _grow = 65536; Uint8List _stream = Uint8List(_grow); @@ -34,50 +71,71 @@ class PdfStream { _stream = newBuffer; } + @override void putByte(int s) { _ensureCapacity(1); _stream[_offset++] = s; } + @override void putBytes(List s) { _ensureCapacity(s.length); _stream.setAll(_offset, s); _offset += s.length; } + @override void setBytes(int offset, Iterable iterable) { _stream.setAll(offset, iterable); } - void putStream(PdfStream s) { + @override + void putStream(PdfStreamBuffer s) { putBytes(s._stream); } + @override int get offset => _offset; Uint8List output() => _stream.sublist(0, _offset); +} - void putString(String? s) { - assert(() { - for (final codeUnit in s!.codeUnits) { - if (codeUnit > 0x7f) { - return false; - } - } - return true; - }()); - putBytes(s!.codeUnits); +class PdfStreamFile extends PdfStream { + PdfStreamFile(File file) { + _raf = file.openSync(mode: FileMode.write); } - void putComment(String s) { - if (s.isEmpty) { - putByte(0x0a); - } else { - for (final l in s.split('\n')) { - if (l.isNotEmpty) { - putBytes('% $l\n'.codeUnits); - } - } - } + late RandomAccessFile _raf; + + void close() { + _raf.closeSync(); + } + + @override + void putByte(int s) { + _raf.writeByteSync(s); + } + + @override + void putBytes(List s) { + _raf.writeFromSync(s); + } + + @override + void setBytes(int offset, Iterable iterable) { + final originalOffset = _raf.positionSync(); + _raf.setPositionSync(offset); + _raf.writeFromSync(iterable.toList()); + _raf.setPositionSync(originalOffset); + } + + @override + void putStream(PdfStreamBuffer s) { + putBytes(s._stream); + } + + @override + int get offset { + return _raf.positionSync(); } } diff --git a/pdf/lib/src/pdf/format/string.dart b/pdf/lib/src/pdf/format/string.dart index 323deac9..07f2d45f 100644 --- a/pdf/lib/src/pdf/format/string.dart +++ b/pdf/lib/src/pdf/format/string.dart @@ -39,7 +39,7 @@ class PdfString extends PdfDataType { } factory PdfString.fromStream( - PdfStream value, { + PdfStreamBuffer value, { PdfStringFormat format = PdfStringFormat.literal, bool encrypted = true, }) { diff --git a/pdf/lib/src/pdf/obj/annotation.dart b/pdf/lib/src/pdf/obj/annotation.dart index 757fa84f..b7c3e311 100644 --- a/pdf/lib/src/pdf/obj/annotation.dart +++ b/pdf/lib/src/pdf/obj/annotation.dart @@ -81,7 +81,7 @@ class PdfChoiceField extends PdfAnnotWidget { params['/V'] = const PdfNull(); } - final buf = PdfStream(); + final buf = PdfStreamBuffer(); final g = PdfGraphics(page, buf); g.setFillColor(textColor); g.setFont(font, fontSize); @@ -928,7 +928,7 @@ class PdfTextField extends PdfFormField { params['/MaxLen'] = PdfNum(maxLength!); } - final buf = PdfStream(); + final buf = PdfStreamBuffer(); final g = PdfGraphics(page, buf); g.setFillColor(textColor); g.setFont(font, fontSize); diff --git a/pdf/lib/src/pdf/obj/object_stream.dart b/pdf/lib/src/pdf/obj/object_stream.dart index f5c6d133..69dcc7dd 100644 --- a/pdf/lib/src/pdf/obj/object_stream.dart +++ b/pdf/lib/src/pdf/obj/object_stream.dart @@ -36,7 +36,7 @@ class PdfObjectStream extends PdfObject { ); /// This holds the stream's content. - final PdfStream buf = PdfStream(); + final PdfStreamBuffer buf = PdfStreamBuffer(); /// defines if the stream needs to be converted to ascii85 final bool isBinary; diff --git a/pdf/lib/src/widgets/document.dart b/pdf/lib/src/widgets/document.dart index 36446c58..924a9c11 100644 --- a/pdf/lib/src/widgets/document.dart +++ b/pdf/lib/src/widgets/document.dart @@ -14,6 +14,7 @@ * limitations under the License. */ +import 'dart:io'; import 'dart:typed_data'; import 'package:xml/xml.dart'; @@ -124,6 +125,16 @@ class Document { _pages.add(page); } + Future saveFile(File file) async { + if (!_paint) { + for (final page in _pages) { + page.postProcess(this); + } + _paint = true; + } + await document.saveFile(file); + } + Future save() async { if (!_paint) { for (final page in _pages) { diff --git a/pdf/lib/src/widgets/page.dart b/pdf/lib/src/widgets/page.dart index 7b34184e..8eb6cc9a 100644 --- a/pdf/lib/src/widgets/page.dart +++ b/pdf/lib/src/widgets/page.dart @@ -69,7 +69,7 @@ class Page { PageOrientation get orientation => pageTheme.orientation; - final BuildCallback _build; + BuildCallback? _build; ThemeData? get theme => pageTheme.theme; @@ -81,6 +81,8 @@ class Page { EdgeInsets? get resolvedMargin => margin?.resolve(pageTheme.textDirection); + bool processed = false; + @protected void debugPaint(Context context) { final _margin = resolvedMargin!; @@ -113,6 +115,11 @@ class Page { } void postProcess(Document document) { + if (processed) { + return; + } + assert(_build != null); + final canvas = _pdfPage!.getGraphics(); canvas.reset(); final _margin = resolvedMargin; @@ -139,7 +146,7 @@ class Page { Widget? content; Widget? foreground; - content = _build(context); + content = _build!(context); final size = layout(content, context, constraints); @@ -181,6 +188,8 @@ class Page { if (foreground != null) { paint(foreground, context); } + processed = true; + _build = null; } @protected diff --git a/pdf/test/file_test.dart b/pdf/test/file_test.dart new file mode 100644 index 00000000..13e97342 --- /dev/null +++ b/pdf/test/file_test.dart @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017, David PHAM-VAN + * + * 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. + */ + +import 'dart:io'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:pdf/pdf.dart'; +import 'package:test/test.dart'; +import 'package:vector_math/vector_math_64.dart'; + +Future createTestPDF(PdfDocument pdf) async { + // PDF Creation takem + final img = Uint32List(10 * 10); + img.fillRange(0, img.length - 1, 0x12345678); + + PdfInfo(pdf, + author: 'David PHAM-VAN', + creator: 'David PHAM-VAN', + title: 'My Title', + subject: 'My Subject'); + final page = PdfPage(pdf, pageFormat: const PdfPageFormat(500, 300)); + + final g = page.getGraphics(); + + g.saveContext(); + var tm = Matrix4.identity(); + tm.translate(10.0, 290); + tm.scale(1.0, -1); + g.setTransform(tm); + g.setColor(const PdfColor(0, 0, 0)); + g.drawShape( + 'M37 0H9C6.24 0 4 2.24 4 5v38c0 2.76 2.24 5 5 5h28c2.76 0 5-2.24 5-5V5c0-2.76-2.24-5-5-5zM23 46c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm15-8H8V6h30v32z', + ); + g.fillPath(); + g.restoreContext(); + + g.saveContext(); + tm = Matrix4.identity(); + tm.translate(200.0, 290); + tm.scale(.1, -.1); + g.setTransform(tm); + g.setColor(const PdfColor(0, 0, 0)); + g.drawShape( + 'M300,200 h-150 a150,150 0 1,0 150,-150 z M275,175 v-150 a150,150 0 0,0 -150,150 z'); + g.strokePath(); + g.restoreContext(); + + final font1 = g.defaultFont; + + final data = File('open-sans.ttf').readAsBytesSync(); + final font2 = PdfTtfFont(pdf, data.buffer.asByteData()); + const s = 'Hello World!'; + final r = font2.stringMetrics(s); + const fs = 20.0; + g.setColor(const PdfColor(0, 1, 1)); + g.drawRect( + 50.0 + r.left * fs, 30.0 + r.top * fs, r.width * fs, r.height * fs); + g.fillPath(); + g.setColor(const PdfColor(0.3, 0.3, 0.3)); + g.drawString(font2, fs, s, 50, 30); + + g.setColor(const PdfColor(1, 0, 0)); + g.drawString(font2, 20, 'Hé (Olà)', 50, 10); + g.drawLine(30, 30, 200, 200); + g.strokePath(); + g.setColor(const PdfColor(1, 0, 0)); + g.drawRect(300, 150, 50, 50); + g.fillPath(); + g.setColor(const PdfColor(0, 0.5, 0)); + final image = + PdfImage(pdf, image: img.buffer.asUint8List(), width: 10, height: 10); + for (var i = 10.0; i < 90.0; i += 5.0) { + g.saveContext(); + final tm = Matrix4.identity(); + tm.rotateZ(i * pi / 360.0); + tm.translate(300.0, -100); + g.setTransform(tm); + g.drawString(font1!, 12, 'Hello $i', 20, 100); + g.drawImage(image, 100, 100); + g.restoreContext(); + } +} + +void main() { + test('Pdf SaveFile', () async { + final file_1 = File('file_test_1.pdf'); + final file_2 = File('file_test_2.pdf'); + { + final pdfMem = PdfDocument(); + await createTestPDF(pdfMem); + await file_1.writeAsBytes(await pdfMem.save()); + } + { + final pdfFile = PdfDocument(); + await createTestPDF(pdfFile); + await file_2.writeAsBytes(await pdfFile.save()); + } + + final len_1 = file_1.lengthSync(); + final len_2 = file_2.lengthSync(); + expect(len_1, len_2); + }); +} diff --git a/pdf/test/minimal_test.dart b/pdf/test/minimal_test.dart index 6bbd53fc..37d745a5 100644 --- a/pdf/test/minimal_test.dart +++ b/pdf/test/minimal_test.dart @@ -70,7 +70,7 @@ void main() { '/Pages': pages.ref(), })); - final os = PdfStream(); + final os = PdfStreamBuffer(); final xref = PdfXrefTable(); xref.objects.addAll([