optimizeToFile method

Future<CPDFOptimizeResult> optimizeToFile(
  1. String outputPath, {
  2. CPDFOptimizeOptions? options,
  3. bool fontSubset = false,
  4. void onProgress(
    1. int pageIndex
    )?,
})

Optimizes the document and writes the result to outputPath, leaving the file the document was opened from untouched.

  • outputPath must be writable, its parent directory must exist, and it must differ from the path of the current document.
  • When options is null the options currently held by the native document are used.
  • fontSubset embeds only the glyphs the document actually uses.
  • The native SDK leaves the opened document unusable once the copy is written. This method reopens it from its original source before returning, so a mounted reader keeps working. Unsaved changes are lost in the process: call save first if they must be kept. If the reopen fails the call throws with code OPTIMIZE_RESTORE_FAILED; the output file is still valid, but the in memory document must be closed and reopened rather than saved.
  • Call cancelOptimize to stop a run early. A cancelled run resolves normally with CPDFOptimizeResult.cancelled set to true.
  • Only one optimization run per document may be in flight. A second call throws with code OPTIMIZE_ALREADY_RUNNING.
  • The default flag set is lossy: it removes every bookmark and every JavaScript action. See CPDFOptimizeFlag.defaults.

Since v3.2.0

Platform differences:

  • iOS ignores fontSubset; the native SDK always writes without font subsetting.
  • iOS allows one optimization run at a time across all documents, Android one run per document. A second run fails with OPTIMIZE_ALREADY_RUNNING.

example:

final result = await document.optimizeToFile(
  '/path/to/optimized.pdf',
  onProgress: (pageIndex) => debugPrint('page $pageIndex'),
);

Implementation

Future<CPDFOptimizeResult> optimizeToFile(
  String outputPath, {
  CPDFOptimizeOptions? options,
  bool fontSubset = false,
  void Function(int pageIndex)? onProgress,
}) async {
  // The trimmed value is both validated and forwarded, so the path the native
  // side resolves is exactly the one that passed this check.
  final trimmedOutputPath = outputPath.trim();
  if (trimmedOutputPath.isEmpty) {
    throw const CPDFOptimizeException(
      'INVALID_OUTPUT_PATH',
      'outputPath must not be empty.',
    );
  }
  // Only the call that actually installed the listener may replace or clear
  // it. The native side allows a single run per document, so a second,
  // overlapping call is rejected anyway; letting it touch the listener would
  // silently stop the progress of the run that is still in flight. The
  // listener is always torn down afterwards, so a failed run cannot leave a
  // stale listener behind.
  final ownsListener =
      onProgress != null && _optimizeProgressListener == null;
  if (ownsListener) {
    _ensureNativeCallHandler();
    _optimizeProgressListener = onProgress;
  }
  try {
    final result = await _channel.invokeMethod('optimize_document_to_file', {
      'output_path': trimmedOutputPath,
      'font_subset': fontSubset,
      if (options != null) 'options': options.toJson(),
    });
    if (result is Map) {
      return CPDFOptimizeResult.fromJson(result);
    }
    throw const CPDFOptimizeException(
      'OPTIMIZE_FAILED',
      'The native side returned no optimize result.',
    );
  } on PlatformException catch (e) {
    throw CPDFOptimizeException(e.code, e.message ?? '');
  } finally {
    if (ownsListener) {
      _optimizeProgressListener = null;
    }
  }
}