MuPDFCore 1.8.0
Multiplatform .NET bindings for MuPDF
Loading...
Searching...
No Matches
MuPDFDocument.cs
1/*
2 MuPDFCore - A set of multiplatform .NET Core bindings for MuPDF.
3 Copyright (C) 2020 Giorgio Bianchini
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as
7 published by the Free Software Foundation, version 3.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Affero General Public License for more details.
13
14 You should have received a copy of the GNU Affero General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>
16*/
17
18using System;
19using System.IO;
20using System.Runtime.InteropServices;
21using System.Text;
22using System.Threading;
23using System.Threading.Tasks;
24
25namespace MuPDFCore
26{
27 /// <summary>
28 /// A wrapper over a MuPDF document object, which contains possibly multiple pages.
29 /// </summary>
30 public class MuPDFDocument : IDisposable
31 {
32 /// <summary>
33 /// If the document is an image, the horizontal resolution of the image. Otherwise, 72.
34 /// </summary>
35 internal double ImageXRes = double.NaN;
36
37 /// <summary>
38 /// If the document is an image, the vertical resolution of the image. Otherwise, 72.
39 /// </summary>
40 internal double ImageYRes = double.NaN;
41
42 /// <summary>
43 /// File extensions corresponding to the supported input formats.
44 /// </summary>
45 private static readonly string[] FileTypeMagics = new[]
46 {
47 ".pdf",
48 ".xps",
49 ".cbz",
50 ".png",
51 ".jpg",
52 ".bmp",
53 ".gif",
54 ".tif",
55 ".pnm",
56 ".pam",
57 ".epub",
58 ".fb2",
59 ".mobi",
60 ".html"
61 };
62
63 /// <summary>
64 /// An <see cref="IDisposable"/> with a value of null.
65 /// </summary>
66 private static IDisposable NullDataHolder = null;
67
68 /// <summary>
69 /// The context that owns this document.
70 /// </summary>
71 private readonly MuPDFContext OwnerContext;
72
73 /// <summary>
74 /// A pointer to the native document object.
75 /// </summary>
76 internal readonly IntPtr NativeDocument;
77
78 /// <summary>
79 /// A pointer to the native stream that was used to create this document (if any).
80 /// </summary>
81 private readonly IntPtr NativeStream = IntPtr.Zero;
82
83 /// <summary>
84 /// The number of pages in the document.
85 /// </summary>
86 private int PageCount;
87
88 /// <summary>
89 /// An <see cref="IDisposable"/> that will be disposed together with this object.
90 /// </summary>
91 private readonly IDisposable DataHolder = null;
92
93 /// <summary>
94 /// A <see cref="GCHandle"/> that will be freed when this object is disposed.
95 /// </summary>
96 private GCHandle? DataHandle = null;
97
98 /// <summary>
99 /// An array of <see cref="MuPDFDisplayList"/>, one for each page in the document.
100 /// </summary>
101 private MuPDFDisplayList[] DisplayLists;
102
103 /// <summary>
104 /// The pages contained in the document.
105 /// </summary>
106 public MuPDFPageCollection Pages { get; private set; }
107
108 /// <summary>
109 /// Defines whether the images resulting from rendering operations should be clipped to the page boundaries.
110 /// </summary>
111 public bool ClipToPageBounds { get; set; } = true;
112
113 /// <summary>
114 /// Describes the encryption state of the document.
115 /// </summary>
116 public EncryptionState EncryptionState { get; private set; }
117
118 /// <summary>
119 /// Describes the restriction state of the document.
120 /// </summary>
121 public RestrictionState RestrictionState { get; private set; }
122
123 /// <summary>
124 /// Describes the operations that are restricted on the document. This is not actually enforced by the library,
125 /// but library users should only allow these operations if the document has been unlocked with the owner password
126 /// (i.e. if <see cref="RestrictionState"/> is <see cref="RestrictionState.Unlocked"/>).
127 /// </summary>
128 public DocumentRestrictions Restrictions { get; private set; }
129
130 /// <summary>
131 /// Create a new <see cref="MuPDFDocument"/> from data bytes accessible through the specified pointer.
132 /// </summary>
133 /// <param name="context">The context that will own this document.</param>
134 /// <param name="dataAddress">A pointer to the data bytes that make up the document.</param>
135 /// <param name="dataLength">The number of bytes to read from the specified address.</param>
136 /// <param name="fileType">The type of the document to read.</param>
137 public MuPDFDocument(MuPDFContext context, IntPtr dataAddress, long dataLength, InputFileTypes fileType) : this(context, dataAddress, dataLength, fileType, ref NullDataHolder) { }
138
139 /// <summary>
140 /// Create a new <see cref="MuPDFDocument"/> from data bytes accessible through the specified pointer.
141 /// </summary>
142 /// <param name="context">The context that will own this document.</param>
143 /// <param name="dataAddress">A pointer to the data bytes that make up the document.</param>
144 /// <param name="dataLength">The number of bytes to read from the specified address.</param>
145 /// <param name="fileType">The type of the document to read.</param>
146 /// <param name="dataHolder">An <see cref="IDisposable"/> that will be disposed when the <see cref="MuPDFDocument"/> is disposed.</param>
147 public MuPDFDocument(MuPDFContext context, IntPtr dataAddress, long dataLength, InputFileTypes fileType, ref IDisposable dataHolder)
148 {
149 bool isImage = fileType == InputFileTypes.BMP || fileType == InputFileTypes.GIF || fileType == InputFileTypes.JPEG || fileType == InputFileTypes.PAM || fileType == InputFileTypes.PNG || fileType == InputFileTypes.PNM || fileType == InputFileTypes.TIFF;
150
151 this.OwnerContext = context;
152
153 float xRes = 0;
154 float yRes = 0;
155
156 ExitCodes result = (ExitCodes)NativeMethods.CreateDocumentFromStream(context.NativeContext, dataAddress, (ulong)dataLength, FileTypeMagics[(int)fileType], isImage ? 1 : 0, ref NativeDocument, ref NativeStream, ref PageCount, ref xRes, ref yRes);
157
158 if (xRes > 72)
159 {
160 this.ImageXRes = xRes;
161 }
162 else
163 {
164 this.ImageXRes = 72;
165 }
166
167 if (yRes > 72)
168 {
169 this.ImageYRes = yRes;
170 }
171 else
172 {
173 this.ImageYRes = 72;
174 }
175
176 this.DataHolder = dataHolder;
177
178 switch (result)
179 {
180 case ExitCodes.EXIT_SUCCESS:
181 break;
182 case ExitCodes.ERR_CANNOT_OPEN_STREAM:
183 throw new MuPDFException("Cannot open data stream", result);
184 case ExitCodes.ERR_CANNOT_OPEN_FILE:
185 throw new MuPDFException("Cannot open document", result);
186 case ExitCodes.ERR_CANNOT_COUNT_PAGES:
187 throw new MuPDFException("Cannot count pages", result);
188 default:
189 throw new MuPDFException("Unknown error", result);
190 }
191
192 if (NativeMethods.CheckIfPasswordNeeded(context.NativeContext, this.NativeDocument) != 0)
193 {
194 this.EncryptionState = EncryptionState.Encrypted;
195 }
196 else
197 {
198 this.EncryptionState = EncryptionState.Unencrypted;
199 }
200
201 int permissions = NativeMethods.GetPermissions(context.NativeContext, this.NativeDocument);
202
203 int restrictions = 0;
204
205 if ((permissions & 1) == 0)
206 {
207 restrictions |= 1;
208 }
209
210 if ((permissions & 2) == 0)
211 {
212 restrictions |= 2;
213 }
214
215 if ((permissions & 4) == 0)
216 {
217 restrictions |= 4;
218 }
219
220 if ((permissions & 8) == 0)
221 {
222 restrictions |= 8;
223 }
224
225 if (restrictions == 0)
226 {
227 this.Restrictions = DocumentRestrictions.None;
228 this.RestrictionState = RestrictionState.Unrestricted;
229 }
230 else
231 {
232 this.Restrictions = (DocumentRestrictions)restrictions;
233 this.RestrictionState = RestrictionState.Restricted;
234 }
235
236 Pages = new MuPDFPageCollection(context, this, PageCount);
237 DisplayLists = new MuPDFDisplayList[PageCount];
238 }
239
240 /// <summary>
241 /// Create a new <see cref="MuPDFDocument"/> from an array of bytes.
242 /// </summary>
243 /// <param name="context">The context that will own this document.</param>
244 /// <param name="data">An array containing the data bytes that make up the document. This must not be altered until after the <see cref="MuPDFDocument"/> has been disposed!
245 /// The address of the array will be pinned, which may cause degradation in the Garbage Collector's performance, and is thus only advised for short-lived documents. To avoid this issue, marshal the bytes to unmanaged memory and use one of the <see cref="IntPtr"/> constructors.</param>
246 /// <param name="fileType">The type of the document to read.</param>
247 public MuPDFDocument(MuPDFContext context, byte[] data, InputFileTypes fileType)
248 {
249 bool isImage = fileType == InputFileTypes.BMP || fileType == InputFileTypes.GIF || fileType == InputFileTypes.JPEG || fileType == InputFileTypes.PAM || fileType == InputFileTypes.PNG || fileType == InputFileTypes.PNM || fileType == InputFileTypes.TIFF;
250
251 this.OwnerContext = context;
252
253 DataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
254 IntPtr dataAddress = DataHandle.Value.AddrOfPinnedObject();
255 ulong dataLength = (ulong)data.Length;
256
257 float xRes = 0;
258 float yRes = 0;
259
260 ExitCodes result = (ExitCodes)NativeMethods.CreateDocumentFromStream(context.NativeContext, dataAddress, dataLength, FileTypeMagics[(int)fileType], isImage ? 1 : 0, ref NativeDocument, ref NativeStream, ref PageCount, ref xRes, ref yRes);
261
262 if (xRes > 72)
263 {
264 this.ImageXRes = xRes;
265 }
266 else
267 {
268 this.ImageXRes = 72;
269 }
270
271 if (yRes > 72)
272 {
273 this.ImageYRes = yRes;
274 }
275 else
276 {
277 this.ImageYRes = 72;
278 }
279
280 switch (result)
281 {
282 case ExitCodes.EXIT_SUCCESS:
283 break;
284 case ExitCodes.ERR_CANNOT_OPEN_STREAM:
285 throw new MuPDFException("Cannot open data stream", result);
286 case ExitCodes.ERR_CANNOT_OPEN_FILE:
287 throw new MuPDFException("Cannot open document", result);
288 case ExitCodes.ERR_CANNOT_COUNT_PAGES:
289 throw new MuPDFException("Cannot count pages", result);
290 default:
291 throw new MuPDFException("Unknown error", result);
292 }
293
294 if (NativeMethods.CheckIfPasswordNeeded(context.NativeContext, this.NativeDocument) != 0)
295 {
296 this.EncryptionState = EncryptionState.Encrypted;
297 }
298 else
299 {
300 this.EncryptionState = EncryptionState.Unencrypted;
301 }
302
303 int permissions = NativeMethods.GetPermissions(context.NativeContext, this.NativeDocument);
304
305 int restrictions = 0;
306
307 if ((permissions & 1) == 0)
308 {
309 restrictions |= 1;
310 }
311
312 if ((permissions & 2) == 0)
313 {
314 restrictions |= 2;
315 }
316
317 if ((permissions & 4) == 0)
318 {
319 restrictions |= 4;
320 }
321
322 if ((permissions & 8) == 0)
323 {
324 restrictions |= 8;
325 }
326
327 if (restrictions == 0)
328 {
329 this.Restrictions = DocumentRestrictions.None;
330 this.RestrictionState = RestrictionState.Unrestricted;
331 }
332 else
333 {
334 this.Restrictions = (DocumentRestrictions)restrictions;
335 this.RestrictionState = RestrictionState.Restricted;
336 }
337
338 Pages = new MuPDFPageCollection(context, this, PageCount);
339 DisplayLists = new MuPDFDisplayList[PageCount];
340 }
341
342 /// <summary>
343 /// Create a new <see cref="MuPDFDocument"/> from a <see cref="MemoryStream"/>.
344 /// </summary>
345 /// <param name="context">The context that will own this document.</param>
346 /// <param name="data">The <see cref="MemoryStream"/> containing the data that makes up the document. This will be disposed when the <see cref="MuPDFDocument"/> has been disposed and must not be disposed externally!
347 /// The address of the <see cref="MemoryStream"/>'s buffer will be pinned, which may cause degradation in the Garbage Collector's performance, and is thus only advised for short-lived documents. To avoid this issue, marshal the bytes to unmanaged memory and use one of the <see cref="IntPtr"/> constructors.</param>
348 /// <param name="fileType">The type of the document to read.</param>
349 public MuPDFDocument(MuPDFContext context, ref MemoryStream data, InputFileTypes fileType)
350 {
351 bool isImage = fileType == InputFileTypes.BMP || fileType == InputFileTypes.GIF || fileType == InputFileTypes.JPEG || fileType == InputFileTypes.PAM || fileType == InputFileTypes.PNG || fileType == InputFileTypes.PNM || fileType == InputFileTypes.TIFF;
352
353 this.OwnerContext = context;
354
355 int origin = (int)data.Seek(0, SeekOrigin.Begin);
356 ulong dataLength = (ulong)data.Length;
357 byte[] dataBytes = data.GetBuffer();
358
359 DataHandle = GCHandle.Alloc(dataBytes, GCHandleType.Pinned);
360 IntPtr dataAddress = IntPtr.Add(DataHandle.Value.AddrOfPinnedObject(), origin);
361
362 DataHolder = data;
363
364 float xRes = 0;
365 float yRes = 0;
366
367 ExitCodes result = (ExitCodes)NativeMethods.CreateDocumentFromStream(context.NativeContext, dataAddress, dataLength, FileTypeMagics[(int)fileType], isImage ? 1 : 0, ref NativeDocument, ref NativeStream, ref PageCount, ref xRes, ref yRes);
368
369 if (xRes > 72)
370 {
371 this.ImageXRes = xRes;
372 }
373 else
374 {
375 this.ImageXRes = 72;
376 }
377
378 if (yRes > 72)
379 {
380 this.ImageYRes = yRes;
381 }
382 else
383 {
384 this.ImageYRes = 72;
385 }
386
387
388 switch (result)
389 {
390 case ExitCodes.EXIT_SUCCESS:
391 break;
392 case ExitCodes.ERR_CANNOT_OPEN_STREAM:
393 throw new MuPDFException("Cannot open data stream", result);
394 case ExitCodes.ERR_CANNOT_OPEN_FILE:
395 throw new MuPDFException("Cannot open document", result);
396 case ExitCodes.ERR_CANNOT_COUNT_PAGES:
397 throw new MuPDFException("Cannot count pages", result);
398 default:
399 throw new MuPDFException("Unknown error", result);
400 }
401
402 if (NativeMethods.CheckIfPasswordNeeded(context.NativeContext, this.NativeDocument) != 0)
403 {
404 this.EncryptionState = EncryptionState.Encrypted;
405 }
406 else
407 {
408 this.EncryptionState = EncryptionState.Unencrypted;
409 }
410
411 int permissions = NativeMethods.GetPermissions(context.NativeContext, this.NativeDocument);
412
413 int restrictions = 0;
414
415 if ((permissions & 1) == 0)
416 {
417 restrictions |= 1;
418 }
419
420 if ((permissions & 2) == 0)
421 {
422 restrictions |= 2;
423 }
424
425 if ((permissions & 4) == 0)
426 {
427 restrictions |= 4;
428 }
429
430 if ((permissions & 8) == 0)
431 {
432 restrictions |= 8;
433 }
434
435 if (restrictions == 0)
436 {
437 this.Restrictions = DocumentRestrictions.None;
438 this.RestrictionState = RestrictionState.Unrestricted;
439 }
440 else
441 {
442 this.Restrictions = (DocumentRestrictions)restrictions;
443 this.RestrictionState = RestrictionState.Restricted;
444 }
445
446 Pages = new MuPDFPageCollection(context, this, PageCount);
447 DisplayLists = new MuPDFDisplayList[PageCount];
448 }
449
450 /// <summary>
451 /// Create a new <see cref="MuPDFDocument"/> from a file.
452 /// </summary>
453 /// <param name="context">The context that will own this document.</param>
454 /// <param name="fileName">The path to the file to open.</param>
455 public MuPDFDocument(MuPDFContext context, string fileName)
456 {
457 bool isImage;
458
459 string extension = Path.GetExtension(fileName).ToLowerInvariant();
460
461 switch (extension)
462 {
463 case ".bmp":
464 case ".dib":
465
466 case ".gif":
467
468 case ".jpg":
469 case ".jpeg":
470 case ".jpe":
471 case ".jif":
472 case ".jfif":
473 case ".jfi":
474
475 case ".pam":
476 case ".pbm":
477 case ".pgm":
478 case ".ppm":
479 case ".pnm":
480
481 case ".png":
482
483 case ".tif":
484 case ".tiff":
485 isImage = true;
486 break;
487 default:
488 isImage = false;
489 break;
490 }
491
492
493 this.OwnerContext = context;
494
495 float xRes = 0;
496 float yRes = 0;
497
498 ExitCodes result;
499
500 using (UTF8EncodedString encodedFileName = new UTF8EncodedString(fileName))
501 {
502 result = (ExitCodes)NativeMethods.CreateDocumentFromFile(context.NativeContext, encodedFileName.Address, isImage ? 1 : 0, ref NativeDocument, ref PageCount, ref xRes, ref yRes);
503 }
504
505 if (xRes > 72)
506 {
507 this.ImageXRes = xRes;
508 }
509 else
510 {
511 this.ImageXRes = 72;
512 }
513
514 if (yRes > 72)
515 {
516 this.ImageYRes = yRes;
517 }
518 else
519 {
520 this.ImageYRes = 72;
521 }
522
523 switch (result)
524 {
525 case ExitCodes.EXIT_SUCCESS:
526 break;
527 case ExitCodes.ERR_CANNOT_OPEN_FILE:
528 throw new MuPDFException("Cannot open document", result);
529 case ExitCodes.ERR_CANNOT_COUNT_PAGES:
530 throw new MuPDFException("Cannot count pages", result);
531 default:
532 throw new MuPDFException("Unknown error", result);
533 }
534
535 if (NativeMethods.CheckIfPasswordNeeded(context.NativeContext, this.NativeDocument) != 0)
536 {
537 this.EncryptionState = EncryptionState.Encrypted;
538 }
539 else
540 {
541 this.EncryptionState = EncryptionState.Unencrypted;
542 }
543
544 int permissions = NativeMethods.GetPermissions(context.NativeContext, this.NativeDocument);
545
546 int restrictions = 0;
547
548 if ((permissions & 1) == 0)
549 {
550 restrictions |= 1;
551 }
552
553 if ((permissions & 2) == 0)
554 {
555 restrictions |= 2;
556 }
557
558 if ((permissions & 4) == 0)
559 {
560 restrictions |= 4;
561 }
562
563 if ((permissions & 8) == 0)
564 {
565 restrictions |= 8;
566 }
567
568 if (restrictions == 0)
569 {
570 this.Restrictions = DocumentRestrictions.None;
571 this.RestrictionState = RestrictionState.Unrestricted;
572 }
573 else
574 {
575 this.Restrictions = (DocumentRestrictions)restrictions;
576 this.RestrictionState = RestrictionState.Restricted;
577 }
578
579 Pages = new MuPDFPageCollection(context, this, PageCount);
580 DisplayLists = new MuPDFDisplayList[PageCount];
581 }
582
583 /// <summary>
584 /// Discard all the display lists that have been loaded from the document, possibly freeing some memory in the case of a huge document.
585 /// </summary>
586 public void ClearCache()
587 {
588 for (int i = 0; i < PageCount; i++)
589 {
590 DisplayLists[i]?.Dispose();
591 DisplayLists[i] = null;
592 }
593 }
594
595 /// <summary>
596 /// Sets the document layout for reflowable document types (e.g., HTML, MOBI). Does not have any effect for documents with a fixed layout (e.g., PDF).
597 /// </summary>
598 /// <param name="width">The width of each page, in points. Must be &gt; 0.</param>
599 /// <param name="height">The height of each page, in points. Must be &gt; 0.</param>
600 /// <param name="em">The default font size, in points.</param>
601 public void Layout(float width, float height, float em)
602 {
603 if (width <= 0)
604 {
605 throw new ArgumentOutOfRangeException(nameof(width), width, "The page width must be greater than 0!");
606 }
607
608 if (height <= 0)
609 {
610 throw new ArgumentOutOfRangeException(nameof(height), height, "The page height must be greater than 0!");
611 }
612
613 this.ClearCache();
614 this.Pages.Dispose();
615
616 NativeMethods.LayoutDocument(this.OwnerContext.NativeContext, this.NativeDocument, width, height, em, out int pageCount);
617
618 this.PageCount = pageCount;
619 this.Pages = new MuPDFPageCollection(this.OwnerContext, this, PageCount);
620 this.DisplayLists = new MuPDFDisplayList[PageCount];
621 }
622
623 /// <summary>
624 /// Sets the document layout for reflowable document types (e.g., HTML, MOBI), so that the document is rendered to a single
625 /// page, as tall as necessary. Does not have any effect for documents with a fixed layout (e.g., PDF).
626 /// </summary>
627 /// <param name="width">The width of each page, in points. Must be &gt; 0.</param>
628 /// <param name="em">The default font size, in points.</param>
629 public void LayoutSinglePage(float width, float em)
630 {
631 if (width <= 0)
632 {
633 throw new ArgumentOutOfRangeException(nameof(width), width, "The page width must be greater than 0!");
634 }
635
636 this.ClearCache();
637 this.Pages.Dispose();
638
639 NativeMethods.LayoutDocument(this.OwnerContext.NativeContext, this.NativeDocument, width, 0, em, out int pageCount);
640
641 this.PageCount = pageCount;
642 this.Pages = new MuPDFPageCollection(this.OwnerContext, this, PageCount);
643 this.DisplayLists = new MuPDFDisplayList[PageCount];
644 }
645
646 /// <summary>
647 /// Render (part of) a page to an array of bytes.
648 /// </summary>
649 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
650 /// <param name="region">The region of the page to render in page units.</param>
651 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
652 /// <param name="pixelFormat">The format of the pixel data.</param>
653 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
654 /// <returns>A byte array containing the raw values for the pixels of the rendered image.</returns>
655 public byte[] Render(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, bool includeAnnotations = true)
656 {
657 if (this.EncryptionState == EncryptionState.Encrypted)
658 {
659 throw new DocumentLockedException("A password is necessary to render the document!");
660 }
661
662 int bufferSize = MuPDFDocument.GetRenderedSize(region, zoom, pixelFormat);
663
664 byte[] buffer = new byte[bufferSize];
665
666 GCHandle bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
667 IntPtr bufferPointer = bufferHandle.AddrOfPinnedObject();
668
669 try
670 {
671 Render(pageNumber, region, zoom, pixelFormat, bufferPointer, includeAnnotations);
672 }
673 finally
674 {
675 bufferHandle.Free();
676 }
677
678 return buffer;
679 }
680
681 /// <summary>
682 /// Render a page to an array of bytes.
683 /// </summary>
684 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
685 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
686 /// <param name="pixelFormat">The format of the pixel data.</param>
687 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
688 /// <returns>A byte array containing the raw values for the pixels of the rendered image.</returns>
689 public byte[] Render(int pageNumber, double zoom, PixelFormats pixelFormat, bool includeAnnotations = true)
690 {
691 if (this.EncryptionState == EncryptionState.Encrypted)
692 {
693 throw new DocumentLockedException("A password is necessary to render the document!");
694 }
695
696 Rectangle region = this.Pages[pageNumber].Bounds;
697 return Render(pageNumber, region, zoom, pixelFormat, includeAnnotations);
698 }
699
700 /// <summary>
701 /// Render (part of) a page to the specified destination.
702 /// </summary>
703 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
704 /// <param name="region">The region of the page to render in page units.</param>
705 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
706 /// <param name="pixelFormat">The format of the pixel data.</param>
707 /// <param name="destination">The address of the buffer where the pixel data will be written. There must be enough space available to write the values for all the pixels, otherwise this will fail catastrophically!</param>
708 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
709 public void Render(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, IntPtr destination, bool includeAnnotations = true)
710 {
711 if (this.EncryptionState == EncryptionState.Encrypted)
712 {
713 throw new DocumentLockedException("A password is necessary to render the document!");
714 }
715
716 if (DisplayLists[pageNumber] == null)
717 {
718 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
719 }
720
721 if (zoom < 0.000001 | zoom * region.Width <= 0.001 || zoom * region.Height <= 0.001)
722 {
723 throw new ArgumentOutOfRangeException(nameof(zoom), zoom, "The zoom factor is too small!");
724 }
725
726 if (this.ImageXRes != 72 || this.ImageYRes != 72)
727 {
728 zoom *= Math.Sqrt(this.ImageXRes * this.ImageYRes) / 72;
729 region = new Rectangle(region.X0 * 72 / this.ImageXRes, region.Y0 * 72 / this.ImageYRes, region.X1 * 72 / this.ImageXRes, region.Y1 * 72 / this.ImageYRes);
730 }
731
732 float fzoom = (float)zoom;
733
734 ExitCodes result = (ExitCodes)NativeMethods.RenderSubDisplayList(OwnerContext.NativeContext, DisplayLists[pageNumber].NativeDisplayList, region.X0, region.Y0, region.X1, region.Y1, fzoom, (int)pixelFormat, destination, IntPtr.Zero);
735
736 switch (result)
737 {
738 case ExitCodes.EXIT_SUCCESS:
739 break;
740 case ExitCodes.ERR_CANNOT_RENDER:
741 throw new MuPDFException("Cannot render page", result);
742 default:
743 throw new MuPDFException("Unknown error", result);
744 }
745
746 RoundedRectangle roundedRegion = region.Round(fzoom);
747 RoundedSize roundedSize = new RoundedSize(roundedRegion.Width, roundedRegion.Height);
748
749 if (pixelFormat == PixelFormats.RGBA || pixelFormat == PixelFormats.BGRA)
750 {
751 Utils.UnpremultiplyAlpha(destination, roundedSize);
752 }
753
754 if (this.ClipToPageBounds && !Pages[pageNumber].Bounds.Contains(DisplayLists[pageNumber].Bounds.Intersect(region)))
755 {
756 Utils.ClipImage(destination, roundedSize, region, Pages[pageNumber].Bounds, pixelFormat);
757 }
758 }
759
760 /// <summary>
761 /// Render a page to the specified destination.
762 /// </summary>
763 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
764 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
765 /// <param name="pixelFormat">The format of the pixel data.</param>
766 /// <param name="destination">The address of the buffer where the pixel data will be written. There must be enough space available to write the values for all the pixels, otherwise this will fail catastrophically!</param>
767 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
768 public void Render(int pageNumber, double zoom, PixelFormats pixelFormat, IntPtr destination, bool includeAnnotations = true)
769 {
770 if (this.EncryptionState == EncryptionState.Encrypted)
771 {
772 throw new DocumentLockedException("A password is necessary to render the document!");
773 }
774
775 Rectangle region = this.Pages[pageNumber].Bounds;
776 Render(pageNumber, region, zoom, pixelFormat, destination, includeAnnotations);
777 }
778
779 /// <summary>
780 /// Render (part of) a page to a <see cref="Span{T}">Span</see>&lt;<see cref="byte"/>&gt;.
781 /// </summary>
782 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
783 /// <param name="region">The region of the page to render in page units.</param>
784 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
785 /// <param name="pixelFormat">The format of the pixel data.</param>
786 /// <param name="disposable">An <see cref="IDisposable"/> that can be used to free the memory where the image is stored. You should keep track of this and dispose it when you have finished working with the image.</param>
787 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
788 public Span<byte> Render(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, out IDisposable disposable, bool includeAnnotations = true)
789 {
790 if (this.EncryptionState == EncryptionState.Encrypted)
791 {
792 throw new DocumentLockedException("A password is necessary to render the document!");
793 }
794
795 int dataSize = GetRenderedSize(region, zoom, pixelFormat);
796
797 IntPtr destination = Marshal.AllocHGlobal(dataSize);
798 disposable = new DisposableIntPtr(destination, dataSize);
799
800 this.Render(pageNumber, region, zoom, pixelFormat, destination, includeAnnotations);
801
802 unsafe
803 {
804 return new Span<byte>((void*)destination, dataSize);
805 }
806 }
807
808 /// <summary>
809 /// Render a page to a <see cref="Span{T}">Span</see>&lt;<see cref="byte"/>&gt;.
810 /// </summary>
811 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
812 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
813 /// <param name="pixelFormat">The format of the pixel data.</param>
814 /// <param name="disposable">An <see cref="IDisposable"/> that can be used to free the memory where the image is stored. You should keep track of this and dispose it when you have finished working with the image.</param>
815 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
816 public Span<byte> Render(int pageNumber, double zoom, PixelFormats pixelFormat, out IDisposable disposable, bool includeAnnotations = true)
817 {
818 if (this.EncryptionState == EncryptionState.Encrypted)
819 {
820 throw new DocumentLockedException("A password is necessary to render the document!");
821 }
822
823 Rectangle region = this.Pages[pageNumber].Bounds;
824 return Render(pageNumber, region, zoom, pixelFormat, out disposable, includeAnnotations);
825 }
826
827
828 /// <summary>
829 /// Create a new <see cref="MuPDFMultiThreadedPageRenderer"/> that renders the specified page with the specified number of threads.
830 /// </summary>
831 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
832 /// <param name="threadCount">The number of threads to use. This must be factorisable using only powers of 2, 3, 5 or 7. Otherwise, the biggest number smaller than <paramref name="threadCount"/> that satisfies this condition is used.</param>
833 /// <returns>A <see cref="MuPDFMultiThreadedPageRenderer"/> that can be used to render the specified page with the specified number of threads.</returns>
834 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
835 public MuPDFMultiThreadedPageRenderer GetMultiThreadedRenderer(int pageNumber, int threadCount, bool includeAnnotations = true)
836 {
837 if (this.EncryptionState == EncryptionState.Encrypted)
838 {
839 throw new DocumentLockedException("A password is necessary to render the document!");
840 }
841
842 if (DisplayLists[pageNumber] == null)
843 {
844 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
845 }
846
847 return new MuPDFMultiThreadedPageRenderer(OwnerContext, DisplayLists[pageNumber], threadCount, Pages[pageNumber].Bounds, this.ClipToPageBounds, this.ImageXRes, this.ImageYRes);
848 }
849
850 /// <summary>
851 /// Determine how many bytes will be necessary to render the specified page at the specified zoom level, using the the specified pixel format.
852 /// </summary>
853 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
854 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
855 /// <param name="pixelFormat">The format of the pixels data.</param>
856 /// <returns>An integer representing the number of bytes that will be necessary to store the pixel data of the rendered image.</returns>
857 public int GetRenderedSize(int pageNumber, double zoom, PixelFormats pixelFormat)
858 {
859 if (this.EncryptionState == EncryptionState.Encrypted)
860 {
861 throw new DocumentLockedException("A password is necessary to render the document!");
862 }
863
864 return GetRenderedSize(Pages[pageNumber].Bounds, zoom, pixelFormat);
865 }
866
867 /// <summary>
868 /// Determine how many bytes will be necessary to render the specified region in page units at the specified zoom level, using the the specified pixel format.
869 /// </summary>
870 /// <param name="region">The region that will be rendered.</param>
871 /// <param name="zoom">The scale at which the region will be rendered. This will determine the size in pixel of the image.</param>
872 /// <param name="pixelFormat">The format of the pixels data.</param>
873 /// <returns>An integer representing the number of bytes that will be necessary to store the pixel data of the rendered image.</returns>
874 public static int GetRenderedSize(Rectangle region, double zoom, PixelFormats pixelFormat)
875 {
876 float x0 = region.X0 * (float)zoom;
877 float y0 = region.Y0 * (float)zoom;
878 float x1 = region.X1 * (float)zoom;
879 float y1 = region.Y1 * (float)zoom;
880
881 Rectangle scaledRect = new Rectangle(x0, y0, x1, y1);
882 RoundedRectangle bounds = scaledRect.Round();
883
884 int width = bounds.Width;
885 int height = bounds.Height;
886
887 switch (pixelFormat)
888 {
889 case PixelFormats.RGB:
890 case PixelFormats.BGR:
891 return width * height * 3;
892 case PixelFormats.RGBA:
893 case PixelFormats.BGRA:
894 return width * height * 4;
895 }
896
897 return -1;
898 }
899
900 /// <summary>
901 /// Save (part of) a page to an image file in the specified format.
902 /// </summary>
903 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
904 /// <param name="region">The region of the page to render in page units.</param>
905 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
906 /// <param name="pixelFormat">The format of the pixel data.</param>
907 /// <param name="fileName">The path to the output file.</param>
908 /// <param name="fileType">The output format of the file.</param>
909 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
910 public void SaveImage(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, string fileName, RasterOutputFileTypes fileType, bool includeAnnotations = true)
911 {
912 if (this.EncryptionState == EncryptionState.Encrypted)
913 {
914 throw new DocumentLockedException("A password is necessary to render the document!");
915 }
916
917 if (pixelFormat == PixelFormats.RGBA && fileType == RasterOutputFileTypes.PNM)
918 {
919 throw new ArgumentException("Cannot save an image with alpha channel in PNM format!", nameof(fileType));
920 }
921
922 if (pixelFormat != PixelFormats.RGB && fileType == RasterOutputFileTypes.JPEG)
923 {
924 throw new ArgumentException("The JPEG format only supports RGB pixel data without an alpha channel!", nameof(fileType));
925 }
926
927 if ((pixelFormat != PixelFormats.RGB && pixelFormat != PixelFormats.RGBA) && fileType == RasterOutputFileTypes.PNG)
928 {
929 throw new ArgumentException("The PNG format only supports RGB or RGBA pixel data!", nameof(fileType));
930 }
931
932 if (DisplayLists[pageNumber] == null)
933 {
934 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
935 }
936
937 if (zoom < 0.000001 | zoom * region.Width <= 0.001 || zoom * region.Height <= 0.001)
938 {
939 throw new ArgumentOutOfRangeException(nameof(zoom), zoom, "The zoom factor is too small!");
940 }
941
942 if (this.ImageXRes != 72 || this.ImageYRes != 72)
943 {
944 zoom *= Math.Sqrt(this.ImageXRes * this.ImageYRes) / 72;
945 region = new Rectangle(region.X0 * 72 / this.ImageXRes, region.Y0 * 72 / this.ImageYRes, region.X1 * 72 / this.ImageXRes, region.Y1 * 72 / this.ImageYRes);
946 }
947
948 float fzoom = (float)zoom;
949
950 ExitCodes result;
951
952 using (UTF8EncodedString encodedFileName = new UTF8EncodedString(fileName))
953 {
954 result = (ExitCodes)NativeMethods.SaveImage(OwnerContext.NativeContext, DisplayLists[pageNumber].NativeDisplayList, region.X0, region.Y0, region.X1, region.Y1, fzoom, (int)pixelFormat, encodedFileName.Address, (int)fileType, 90);
955 }
956
957 switch (result)
958 {
959 case ExitCodes.EXIT_SUCCESS:
960 break;
961 case ExitCodes.ERR_CANNOT_RENDER:
962 throw new MuPDFException("Cannot render page", result);
963 case ExitCodes.ERR_CANNOT_SAVE:
964 throw new MuPDFException("Cannot save to the output file", result);
965 default:
966 throw new MuPDFException("Unknown error", result);
967 }
968 }
969
970 /// <summary>
971 /// Save (part of) a page to an image file in JPEG format, with the specified quality.
972 /// </summary>
973 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
974 /// <param name="region">The region of the page to render in page units.</param>
975 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
976 /// <param name="fileName">The path to the output file.</param>
977 /// <param name="quality">The quality of the JPEG output file (ranging from 0 to 100).</param>
978 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
979 public void SaveImageAsJPEG(int pageNumber, Rectangle region, double zoom, string fileName, int quality, bool includeAnnotations = true)
980 {
981 if (this.EncryptionState == EncryptionState.Encrypted)
982 {
983 throw new DocumentLockedException("A password is necessary to render the document!");
984 }
985
986 if (quality < 0 || quality > 100)
987 {
988 throw new ArgumentOutOfRangeException(nameof(quality), quality, "The JPEG quality must range between 0 and 100 (inclusive)!");
989 }
990
991 if (DisplayLists[pageNumber] == null)
992 {
993 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
994 }
995
996 if (zoom < 0.000001 | zoom * region.Width <= 0.001 || zoom * region.Height <= 0.001)
997 {
998 throw new ArgumentOutOfRangeException(nameof(zoom), zoom, "The zoom factor is too small!");
999 }
1000
1001 if (this.ImageXRes != 72 || this.ImageYRes != 72)
1002 {
1003 zoom *= Math.Sqrt(this.ImageXRes * this.ImageYRes) / 72;
1004 region = new Rectangle(region.X0 * 72 / this.ImageXRes, region.Y0 * 72 / this.ImageYRes, region.X1 * 72 / this.ImageXRes, region.Y1 * 72 / this.ImageYRes);
1005 }
1006
1007 float fzoom = (float)zoom;
1008
1009 ExitCodes result;
1010
1011 using (UTF8EncodedString encodedFileName = new UTF8EncodedString(fileName))
1012 {
1013 result = (ExitCodes)NativeMethods.SaveImage(OwnerContext.NativeContext, DisplayLists[pageNumber].NativeDisplayList, region.X0, region.Y0, region.X1, region.Y1, fzoom, (int)PixelFormats.RGB, encodedFileName.Address, (int)RasterOutputFileTypes.JPEG, quality);
1014 }
1015
1016 switch (result)
1017 {
1018 case ExitCodes.EXIT_SUCCESS:
1019 break;
1020 case ExitCodes.ERR_CANNOT_RENDER:
1021 throw new MuPDFException("Cannot render page", result);
1022 case ExitCodes.ERR_CANNOT_SAVE:
1023 throw new MuPDFException("Cannot save to the output file", result);
1024 default:
1025 throw new MuPDFException("Unknown error", result);
1026 }
1027 }
1028
1029 /// <summary>
1030 /// Save a page to an image file in the specified format.
1031 /// </summary>
1032 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
1033 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
1034 /// <param name="pixelFormat">The format of the pixel data.</param>
1035 /// <param name="fileName">The path to the output file.</param>
1036 /// <param name="fileType">The output format of the file.</param>
1037 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
1038 public void SaveImage(int pageNumber, double zoom, PixelFormats pixelFormat, string fileName, RasterOutputFileTypes fileType, bool includeAnnotations = true)
1039 {
1040 if (this.EncryptionState == EncryptionState.Encrypted)
1041 {
1042 throw new DocumentLockedException("A password is necessary to render the document!");
1043 }
1044
1045 Rectangle region = this.Pages[pageNumber].Bounds;
1046 SaveImage(pageNumber, region, zoom, pixelFormat, fileName, fileType, includeAnnotations);
1047 }
1048
1049 /// <summary>
1050 /// Save a page to an image file in JPEG format, with the specified quality.
1051 /// </summary>
1052 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
1053 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
1054 /// <param name="fileName">The path to the output file.</param>
1055 /// <param name="quality">The quality of the JPEG output file (ranging from 0 to 100).</param>
1056 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
1057 public void SaveImageAsJPEG(int pageNumber, double zoom, string fileName, int quality, bool includeAnnotations = true)
1058 {
1059 if (this.EncryptionState == EncryptionState.Encrypted)
1060 {
1061 throw new DocumentLockedException("A password is necessary to render the document!");
1062 }
1063
1064 Rectangle region = this.Pages[pageNumber].Bounds;
1065 SaveImageAsJPEG(pageNumber, region, zoom, fileName, quality, includeAnnotations);
1066 }
1067
1068 /// <summary>
1069 /// Write (part of) a page to an image stream in the specified format.
1070 /// </summary>
1071 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
1072 /// <param name="region">The region of the page to render in page units.</param>
1073 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
1074 /// <param name="pixelFormat">The format of the pixel data.</param>
1075 /// <param name="outputStream">The stream to which the image data will be written.</param>
1076 /// <param name="fileType">The output format of the image.</param>
1077 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
1078 public void WriteImage(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, Stream outputStream, RasterOutputFileTypes fileType, bool includeAnnotations = true)
1079 {
1080 if (this.EncryptionState == EncryptionState.Encrypted)
1081 {
1082 throw new DocumentLockedException("A password is necessary to render the document!");
1083 }
1084
1085 if (pixelFormat == PixelFormats.RGBA && fileType == RasterOutputFileTypes.PNM)
1086 {
1087 throw new ArgumentException("Cannot save an image with alpha channel in PNM format!", nameof(fileType));
1088 }
1089
1090 if (pixelFormat != PixelFormats.RGB && fileType == RasterOutputFileTypes.JPEG)
1091 {
1092 throw new ArgumentException("The JPEG format only supports RGB pixel data without an alpha channel!", nameof(fileType));
1093 }
1094
1095 if ((pixelFormat != PixelFormats.RGB && pixelFormat != PixelFormats.RGBA) && fileType == RasterOutputFileTypes.PNG)
1096 {
1097 throw new ArgumentException("The PNG format only supports RGB or RGBA pixel data!", nameof(fileType));
1098 }
1099
1100 if (DisplayLists[pageNumber] == null)
1101 {
1102 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
1103 }
1104
1105 if (zoom < 0.000001 | zoom * region.Width <= 0.001 || zoom * region.Height <= 0.001)
1106 {
1107 throw new ArgumentOutOfRangeException(nameof(zoom), zoom, "The zoom factor is too small!");
1108 }
1109
1110 if (this.ImageXRes != 72 || this.ImageYRes != 72)
1111 {
1112 zoom *= Math.Sqrt(this.ImageXRes * this.ImageYRes) / 72;
1113 region = new Rectangle(region.X0 * 72 / this.ImageXRes, region.Y0 * 72 / this.ImageYRes, region.X1 * 72 / this.ImageXRes, region.Y1 * 72 / this.ImageYRes);
1114 }
1115
1116 float fzoom = (float)zoom;
1117
1118 IntPtr outputBuffer = IntPtr.Zero;
1119 IntPtr outputData = IntPtr.Zero;
1120 ulong outputDataLength = 0;
1121
1122 ExitCodes result = (ExitCodes)NativeMethods.WriteImage(OwnerContext.NativeContext, DisplayLists[pageNumber].NativeDisplayList, region.X0, region.Y0, region.X1, region.Y1, fzoom, (int)pixelFormat, (int)fileType, 90, ref outputBuffer, ref outputData, ref outputDataLength);
1123
1124 switch (result)
1125 {
1126 case ExitCodes.EXIT_SUCCESS:
1127 break;
1128 case ExitCodes.ERR_CANNOT_RENDER:
1129 throw new MuPDFException("Cannot render page", result);
1130 case ExitCodes.ERR_CANNOT_CREATE_CONTEXT:
1131 throw new MuPDFException("Cannot create the output buffer", result);
1132 default:
1133 throw new MuPDFException("Unknown error", result);
1134 }
1135
1136 byte[] buffer = new byte[1024];
1137
1138 while (outputDataLength > 0)
1139 {
1140 int bytesToCopy = (int)Math.Min(buffer.Length, (long)outputDataLength);
1141 Marshal.Copy(outputData, buffer, 0, bytesToCopy);
1142 outputData = IntPtr.Add(outputData, bytesToCopy);
1143 outputStream.Write(buffer, 0, bytesToCopy);
1144 outputDataLength -= (ulong)bytesToCopy;
1145 }
1146
1147 NativeMethods.DisposeBuffer(OwnerContext.NativeContext, outputBuffer);
1148 }
1149
1150
1151 /// <summary>
1152 /// Write (part of) a page to an image stream in JPEG format, with the specified quality.
1153 /// </summary>
1154 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
1155 /// <param name="region">The region of the page to render in page units.</param>
1156 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
1157 /// <param name="outputStream">The stream to which the image data will be written.</param>
1158 /// <param name="quality">The quality of the JPEG output (ranging from 0 to 100).</param>
1159 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
1160 public void WriteImageAsJPEG(int pageNumber, Rectangle region, double zoom, Stream outputStream, int quality, bool includeAnnotations = true)
1161 {
1162 if (this.EncryptionState == EncryptionState.Encrypted)
1163 {
1164 throw new DocumentLockedException("A password is necessary to render the document!");
1165 }
1166
1167 if (quality < 0 || quality > 100)
1168 {
1169 throw new ArgumentOutOfRangeException(nameof(quality), quality, "The JPEG quality must range between 0 and 100 (inclusive)!");
1170 }
1171
1172 if (DisplayLists[pageNumber] == null)
1173 {
1174 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
1175 }
1176
1177 if (zoom < 0.000001 | zoom * region.Width <= 0.001 || zoom * region.Height <= 0.001)
1178 {
1179 throw new ArgumentOutOfRangeException(nameof(zoom), zoom, "The zoom factor is too small!");
1180 }
1181
1182 if (this.ImageXRes != 72 || this.ImageYRes != 72)
1183 {
1184 zoom *= Math.Sqrt(this.ImageXRes * this.ImageYRes) / 72;
1185 region = new Rectangle(region.X0 * 72 / this.ImageXRes, region.Y0 * 72 / this.ImageYRes, region.X1 * 72 / this.ImageXRes, region.Y1 * 72 / this.ImageYRes);
1186 }
1187
1188 float fzoom = (float)zoom;
1189
1190 IntPtr outputBuffer = IntPtr.Zero;
1191 IntPtr outputData = IntPtr.Zero;
1192 ulong outputDataLength = 0;
1193
1194 ExitCodes result = (ExitCodes)NativeMethods.WriteImage(OwnerContext.NativeContext, DisplayLists[pageNumber].NativeDisplayList, region.X0, region.Y0, region.X1, region.Y1, fzoom, (int)PixelFormats.RGB, (int)RasterOutputFileTypes.JPEG, quality, ref outputBuffer, ref outputData, ref outputDataLength);
1195
1196 switch (result)
1197 {
1198 case ExitCodes.EXIT_SUCCESS:
1199 break;
1200 case ExitCodes.ERR_CANNOT_RENDER:
1201 throw new MuPDFException("Cannot render page", result);
1202 case ExitCodes.ERR_CANNOT_CREATE_CONTEXT:
1203 throw new MuPDFException("Cannot create the output buffer", result);
1204 default:
1205 throw new MuPDFException("Unknown error", result);
1206 }
1207
1208 byte[] buffer = new byte[1024];
1209
1210 while (outputDataLength > 0)
1211 {
1212 int bytesToCopy = (int)Math.Min(buffer.Length, (long)outputDataLength);
1213 Marshal.Copy(outputData, buffer, 0, bytesToCopy);
1214 outputData = IntPtr.Add(outputData, bytesToCopy);
1215 outputStream.Write(buffer, 0, bytesToCopy);
1216 outputDataLength -= (ulong)bytesToCopy;
1217 }
1218
1219 NativeMethods.DisposeBuffer(OwnerContext.NativeContext, outputBuffer);
1220 }
1221
1222 /// <summary>
1223 /// Write a page to an image stream in the specified format.
1224 /// </summary>
1225 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
1226 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
1227 /// <param name="pixelFormat">The format of the pixel data.</param>
1228 /// <param name="outputStream">The stream to which the image data will be written.</param>
1229 /// <param name="fileType">The output format of the image.</param>
1230 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
1231 public void WriteImage(int pageNumber, double zoom, PixelFormats pixelFormat, Stream outputStream, RasterOutputFileTypes fileType, bool includeAnnotations = true)
1232 {
1233 if (this.EncryptionState == EncryptionState.Encrypted)
1234 {
1235 throw new DocumentLockedException("A password is necessary to render the document!");
1236 }
1237
1238 Rectangle region = this.Pages[pageNumber].Bounds;
1239 WriteImage(pageNumber, region, zoom, pixelFormat, outputStream, fileType, includeAnnotations);
1240 }
1241
1242 /// <summary>
1243 /// Write a page to an image stream in JPEG format, with the specified quality.
1244 /// </summary>
1245 /// <param name="pageNumber">The number of the page to render (starting at 0).</param>
1246 /// <param name="zoom">The scale at which the page will be rendered. This will determine the size in pixel of the image.</param>
1247 /// <param name="outputStream">The stream to which the image data will be written.</param>
1248 /// <param name="quality">The quality of the JPEG output (ranging from 0 to 100).</param>
1249 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
1250 public void WriteImageAsJPEG(int pageNumber, double zoom, Stream outputStream, int quality, bool includeAnnotations = true)
1251 {
1252 if (this.EncryptionState == EncryptionState.Encrypted)
1253 {
1254 throw new DocumentLockedException("A password is necessary to render the document!");
1255 }
1256
1257 Rectangle region = this.Pages[pageNumber].Bounds;
1258 WriteImageAsJPEG(pageNumber, region, zoom, outputStream, quality, includeAnnotations);
1259 }
1260
1261 /// <summary>
1262 /// Create a new document containing the specified (parts of) pages from other documents.
1263 /// </summary>
1264 /// <param name="context">The context that was used to open the documents.</param>
1265 /// <param name="fileName">The output file name.</param>
1266 /// <param name="fileType">The output file format.</param>
1267 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
1268 /// <param name="pages">The pages to include in the document. The "page" element specifies the page, the "region" element the area of the page that should be included in the document, and the "zoom" element how much the region should be scaled.</param>
1269 public static void CreateDocument(MuPDFContext context, string fileName, DocumentOutputFileTypes fileType, bool includeAnnotations = true, params (MuPDFPage page, Rectangle region, float zoom)[] pages)
1270 {
1271 if (fileType == DocumentOutputFileTypes.SVG && pages.Length > 1)
1272 {
1273 //Actually, you can, but the library creates multiple files appending numbers after each name (e.g. page1.svg, page2.svg, ...), which is ugly and may have unintended consequences.
1274 //If you really want to do this, you can call this method multiple times.
1275 throw new ArgumentException("You cannot create an SVG document with more than one page!", nameof(pages));
1276 }
1277
1278 string originalFileName = fileName;
1279
1280 if (fileType == DocumentOutputFileTypes.SVG)
1281 {
1282 //For SVG documents, the library annoyingly alters the output file name, appending a "1" just before the extension (e.g. document.svg -> document1.svg). Since users may not be expecting this, it is best to render to a temporary file and then move it to the specified location.
1283 fileName = Path.GetTempFileName();
1284 }
1285
1286 IntPtr documentWriter = IntPtr.Zero;
1287
1288 ExitCodes result;
1289
1290 // Encode the file name in UTF-8 in unmanaged memory.
1291 using (UTF8EncodedString encodedFileName = new UTF8EncodedString(fileName))
1292 {
1293 //Initialise document writer.
1294 result = (ExitCodes)NativeMethods.CreateDocumentWriter(context.NativeContext, encodedFileName.Address, (int)fileType, ref documentWriter);
1295 }
1296
1297 switch (result)
1298 {
1299 case ExitCodes.EXIT_SUCCESS:
1300 break;
1301 case ExitCodes.ERR_CANNOT_CREATE_WRITER:
1302 throw new MuPDFException("Cannot create the document writer", result);
1303 default:
1304 throw new MuPDFException("Unknown error", result);
1305 }
1306
1307 //Write pages.
1308 for (int i = 0; i < pages.Length; i++)
1309 {
1310 MuPDFDocument doc = pages[i].page.OwnerDocument;
1311 int pageNum = pages[i].page.PageNumber;
1312
1313 if (doc.DisplayLists[pageNum] == null)
1314 {
1315 doc.DisplayLists[pageNum] = new MuPDFDisplayList(doc.OwnerContext, doc.Pages[pageNum], includeAnnotations);
1316 }
1317
1318 Rectangle region = pages[i].region;
1319 double zoom = pages[i].zoom;
1320
1321 if (pages[i].page.OwnerDocument.ImageXRes != 72 || pages[i].page.OwnerDocument.ImageYRes != 72)
1322 {
1323 zoom *= Math.Sqrt(pages[i].page.OwnerDocument.ImageXRes * pages[i].page.OwnerDocument.ImageYRes) / 72;
1324 region = new Rectangle(region.X0 * 72 / pages[i].page.OwnerDocument.ImageXRes, region.Y0 * 72 / pages[i].page.OwnerDocument.ImageYRes, region.X1 * 72 / pages[i].page.OwnerDocument.ImageXRes, region.Y1 * 72 / pages[i].page.OwnerDocument.ImageYRes);
1325 }
1326
1327 result = (ExitCodes)NativeMethods.WriteSubDisplayListAsPage(context.NativeContext, doc.DisplayLists[pageNum].NativeDisplayList, region.X0, region.Y0, region.X1, region.Y1, (float)zoom, documentWriter);
1328
1329 switch (result)
1330 {
1331 case ExitCodes.EXIT_SUCCESS:
1332 break;
1333 case ExitCodes.ERR_CANNOT_RENDER:
1334 throw new MuPDFException("Cannot render page " + i.ToString(), result);
1335 default:
1336 throw new MuPDFException("Unknown error", result);
1337 }
1338 }
1339
1340 //Close and dispose the document writer.
1341 result = (ExitCodes)NativeMethods.FinalizeDocumentWriter(context.NativeContext, documentWriter);
1342
1343 switch (result)
1344 {
1345 case ExitCodes.EXIT_SUCCESS:
1346 break;
1347 case ExitCodes.ERR_CANNOT_CLOSE_DOCUMENT:
1348 throw new MuPDFException("Cannot finalise the document", result);
1349 default:
1350 throw new MuPDFException("Unknown error", result);
1351 }
1352
1353 if (fileType == DocumentOutputFileTypes.SVG)
1354 {
1355 //Move the temporary file to the location specified by the user.
1356 //The library has altered the temporary file name by appending a "1" before the extension.
1357 string tempFileName = Path.Combine(Path.GetDirectoryName(fileName), Path.GetFileNameWithoutExtension(fileName) + "1" + Path.GetExtension(fileName));
1358
1359 //Overwrite existing file.
1360 if (File.Exists(originalFileName))
1361 {
1362 File.Delete(originalFileName);
1363 }
1364
1365 File.Move(tempFileName, originalFileName);
1366 }
1367 }
1368
1369 /// <summary>
1370 /// Create a new document containing the specified pages from other documents.
1371 /// </summary>
1372 /// <param name="context">The context that was used to open the documents.</param>
1373 /// <param name="fileName">The output file name.</param>
1374 /// <param name="fileType">The output file format.</param>
1375 /// <param name="pages">The pages to include in the document.</param>
1376 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included in the display list that is generated. Otherwise, only the page contents are included.</param>
1377 public static void CreateDocument(MuPDFContext context, string fileName, DocumentOutputFileTypes fileType, bool includeAnnotations = true, params MuPDFPage[] pages)
1378 {
1379 (MuPDFPage, Rectangle, float)[] boundedPages = new (MuPDFPage, Rectangle, float)[pages.Length];
1380
1381 for (int i = 0; i < pages.Length; i++)
1382 {
1383 boundedPages[i] = (pages[i], pages[i].Bounds, 1);
1384 }
1385
1386 CreateDocument(context, fileName, fileType, includeAnnotations, boundedPages);
1387 }
1388
1389
1390 /// <summary>
1391 /// Creates a new <see cref="MuPDFStructuredTextPage"/> from the specified page. This contains information about the text layout that can be used for highlighting and searching. The reading order is taken from the order the text is drawn in the source file, so may not be accurate.
1392 /// </summary>
1393 /// <param name="pageNumber">The number of the page (starting at 0)</param>
1394 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included. Otherwise, only the page contents are included.</param>
1395 /// <returns>A <see cref="MuPDFStructuredTextPage"/> containing a structured text representation of the page.</returns>
1396 public MuPDFStructuredTextPage GetStructuredTextPage(int pageNumber, bool includeAnnotations = true)
1397 {
1398 if (this.EncryptionState == EncryptionState.Encrypted)
1399 {
1400 throw new DocumentLockedException("A password is necessary to render the document!");
1401 }
1402
1403 if (DisplayLists[pageNumber] == null)
1404 {
1405 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
1406 }
1407
1408 return new MuPDFStructuredTextPage(this.OwnerContext, this.DisplayLists[pageNumber], null, 1, new Rectangle());
1409 }
1410
1411 /// <summary>
1412 /// Creates a new <see cref="MuPDFStructuredTextPage"/> from the specified page, using optical character recognition (OCR) to determine what text is written on the image. This contains information about the text layout that can be used for highlighting and searching.
1413 /// </summary>
1414 /// <param name="pageNumber">The number of the page (starting at 0)</param>
1415 /// <param name="ocrLanguage">The language to use for optical character recognition (OCR). If this is null, no OCR is performed.</param>
1416 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included. Otherwise, only the page contents are included.</param>
1417 /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation. Providing a value other than the default is not supported on Windows x86 and will throw a runtime exception.</param>
1418 /// <param name="progress">An <see cref="IProgress{OCRProgressInfo}"/> used to report progress. Providing a value other than null is not supported on Windows x86 and will throw a runtime exception.</param>
1419 /// <returns>A <see cref="MuPDFStructuredTextPage"/> containing a structured text representation of the page.</returns>
1420 public MuPDFStructuredTextPage GetStructuredTextPage(int pageNumber, TesseractLanguage ocrLanguage, bool includeAnnotations = true, CancellationToken cancellationToken = default, IProgress<OCRProgressInfo> progress = null)
1421 {
1422 if (this.EncryptionState == EncryptionState.Encrypted)
1423 {
1424 throw new DocumentLockedException("A password is necessary to render the document!");
1425 }
1426
1427 if (DisplayLists[pageNumber] == null)
1428 {
1429 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
1430 }
1431
1432 double zoom = 1;
1433 Rectangle region = this.Pages[pageNumber].Bounds;
1434
1435 if (this.ImageXRes != 72 || this.ImageYRes != 72)
1436 {
1437 zoom *= Math.Sqrt(this.ImageXRes * this.ImageYRes) / 72;
1438 region = new Rectangle(region.X0 * 72 / this.ImageXRes, region.Y0 * 72 / this.ImageYRes, region.X1 * 72 / this.ImageXRes, region.Y1 * 72 / this.ImageYRes);
1439 }
1440
1441 return new MuPDFStructuredTextPage(this.OwnerContext, this.DisplayLists[pageNumber], ocrLanguage, zoom, region, cancellationToken, progress);
1442 }
1443
1444 /// <summary>
1445 /// Creates a new <see cref="MuPDFStructuredTextPage"/> from the specified page, using optical character recognition (OCR) to determine what text is written on the image. This contains information about the text layout that can be used for highlighting and searching. The OCR step is run asynchronously, e.g. to avoid blocking the UI thread.
1446 /// </summary>
1447 /// <param name="pageNumber">The number of the page (starting at 0)</param>
1448 /// <param name="ocrLanguage">The language to use for optical character recognition (OCR). If this is null, no OCR is performed.</param>
1449 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included. Otherwise, only the page contents are included.</param>
1450 /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation. Providing a value other than the default is not supported on Windows x86 and will throw a runtime exception.</param>
1451 /// <param name="progress">An <see cref="IProgress{OCRProgressInfo}"/> used to report progress. Providing a value other than null is not supported on Windows x86 and will throw a runtime exception.</param>
1452 /// <returns>A <see cref="MuPDFStructuredTextPage"/> containing a structured text representation of the page.</returns>
1453 public async Task<MuPDFStructuredTextPage> GetStructuredTextPageAsync(int pageNumber, TesseractLanguage ocrLanguage, bool includeAnnotations = true, CancellationToken cancellationToken = default, IProgress<OCRProgressInfo> progress = null)
1454 {
1455 if (this.EncryptionState == EncryptionState.Encrypted)
1456 {
1457 throw new DocumentLockedException("A password is necessary to render the document!");
1458 }
1459
1460 if (DisplayLists[pageNumber] == null)
1461 {
1462 DisplayLists[pageNumber] = new MuPDFDisplayList(this.OwnerContext, this.Pages[pageNumber], includeAnnotations);
1463 }
1464
1465 double zoom = 1;
1466 Rectangle region = this.Pages[pageNumber].Bounds;
1467
1468 if (this.ImageXRes != 72 || this.ImageYRes != 72)
1469 {
1470 zoom *= Math.Sqrt(this.ImageXRes * this.ImageYRes) / 72;
1471 region = new Rectangle(region.X0 * 72 / this.ImageXRes, region.Y0 * 72 / this.ImageYRes, region.X1 * 72 / this.ImageXRes, region.Y1 * 72 / this.ImageYRes);
1472 }
1473
1474 return await Task.Run(() => new MuPDFStructuredTextPage(this.OwnerContext, this.DisplayLists[pageNumber], ocrLanguage, zoom, region, cancellationToken, progress));
1475 }
1476
1477 /// <summary>
1478 /// Extracts all the text from the document and returns it as a <see cref="string"/>. The reading order is taken from the order the text is drawn in the source file, so may not be accurate.
1479 /// </summary>
1480 /// <param name="separator">The character(s) used to separate the text lines obtained from the document. If this is <see langword="null" />, <see cref="Environment.NewLine"/> is used as a default separator.</param>
1481 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included. Otherwise, only the page contents are included.</param>
1482 /// <returns>A <see cref="string"/> containing all the text in the document. Characters are converted from the UTF-8 representation used in the document to equivalent UTF-16 <see cref="string"/>s.</returns>
1483 public string ExtractText(string separator = null, bool includeAnnotations = true)
1484 {
1485 if (this.EncryptionState == EncryptionState.Encrypted)
1486 {
1487 throw new DocumentLockedException("A password is necessary to render the document!");
1488 }
1489
1490 separator = separator ?? Environment.NewLine;
1491
1492 var text = new StringBuilder();
1493 bool started = false;
1494
1495 for (int i = 0; i < this.Pages.Count; i++)
1496 {
1497 MuPDFStructuredTextPage structuredTextPage = this.GetStructuredTextPage(i, includeAnnotations);
1498 foreach (MuPDFStructuredTextBlock textBlock in structuredTextPage.StructuredTextBlocks)
1499 {
1500 var numLines = textBlock.Count;
1501 for (var j = 0; j < numLines; j++)
1502 {
1503 if (!string.IsNullOrWhiteSpace(textBlock[j].Text))
1504 {
1505 if (started)
1506 {
1507 text.Append(separator);
1508 }
1509 else
1510 {
1511 started = true;
1512 }
1513
1514 text.Append(textBlock[j].Text);
1515 }
1516 }
1517 }
1518 }
1519
1520 return text.ToString();
1521 }
1522
1523 /// <summary>
1524 /// Extracts all the text from the document and returns it as a <see cref="string"/>, using optical character recognition (OCR) to determine what text is written on the image.
1525 /// </summary>
1526 /// <param name="separator">The character(s) used to separate the text lines obtained from the document. If this is <see langword="null" />, <see cref="Environment.NewLine"/> is used as a default separator.</param>
1527 /// <param name="ocrLanguage">The language to use for optical character recognition (OCR). If this is null, no OCR is performed.</param>
1528 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included. Otherwise, only the page contents are included.</param>
1529 /// <returns>A <see cref="string"/> containing all the text in the document. Characters are converted from the UTF-8 representation used in the document to equivalent UTF-16 <see cref="string"/>s.</returns>
1530 public string ExtractText(TesseractLanguage ocrLanguage, string separator = null, bool includeAnnotations = true)
1531 {
1532 if (this.EncryptionState == EncryptionState.Encrypted)
1533 {
1534 throw new DocumentLockedException("A password is necessary to render the document!");
1535 }
1536
1537 separator = separator ?? Environment.NewLine;
1538
1539 var text = new StringBuilder();
1540 bool started = false;
1541
1542 for (int i = 0; i < this.Pages.Count; i++)
1543 {
1544 MuPDFStructuredTextPage structuredTextPage = this.GetStructuredTextPage(i, ocrLanguage, includeAnnotations);
1545 foreach (MuPDFStructuredTextBlock textBlock in structuredTextPage.StructuredTextBlocks)
1546 {
1547 var numLines = textBlock.Count;
1548 for (var j = 0; j < numLines; j++)
1549 {
1550 if (!string.IsNullOrWhiteSpace(textBlock[j].Text))
1551 {
1552 if (started)
1553 {
1554 text.Append(separator);
1555 }
1556 else
1557 {
1558 started = true;
1559 }
1560
1561 text.Append(textBlock[j].Text);
1562 }
1563 }
1564 }
1565 }
1566
1567 return text.ToString();
1568 }
1569
1570 /// <summary>
1571 /// Extracts all the text from the document and returns it as a <see cref="string"/>, using optical character recognition (OCR) to determine what text is written on the image. The OCR step is run asynchronously, e.g. to avoid blocking the UI thread.
1572 /// </summary>
1573 /// <param name="separator">The character(s) used to separate the text lines obtained from the document. If this is <see langword="null" />, <see cref="Environment.NewLine"/> is used as a default separator.</param>
1574 /// <param name="ocrLanguage">The language to use for optical character recognition (OCR). If this is null, no OCR is performed.</param>
1575 /// <param name="includeAnnotations">If this is <see langword="true" />, annotations (e.g. signatures) are included. Otherwise, only the page contents are included.</param>
1576 /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
1577 /// <param name="progress">An <see cref="IProgress{OCRProgressInfo}"/> used to report progress.</param>
1578 /// <returns>A <see cref="string"/> containing all the text in the document. Characters are converted from the UTF-8 representation used in the document to equivalent UTF-16 <see cref="string"/>s.</returns>
1579 public async Task<string> ExtractTextAsync(TesseractLanguage ocrLanguage, string separator = null, bool includeAnnotations = true, CancellationToken cancellationToken = default, IProgress<OCRProgressInfo> progress = null)
1580 {
1581 if (this.EncryptionState == EncryptionState.Encrypted)
1582 {
1583 throw new DocumentLockedException("A password is necessary to render the document!");
1584 }
1585
1586 separator = separator ?? Environment.NewLine;
1587
1588 var text = new StringBuilder();
1589 bool started = false;
1590
1591 for (int i = 0; i < this.Pages.Count; i++)
1592 {
1593 MuPDFStructuredTextPage structuredTextPage = await this.GetStructuredTextPageAsync(i, ocrLanguage, includeAnnotations, cancellationToken, progress);
1594 foreach (MuPDFStructuredTextBlock textBlock in structuredTextPage.StructuredTextBlocks)
1595 {
1596 var numLines = textBlock.Count;
1597 for (var j = 0; j < numLines; j++)
1598 {
1599 if (!string.IsNullOrWhiteSpace(textBlock[j].Text))
1600 {
1601 if (started)
1602 {
1603 text.Append(separator);
1604 }
1605 else
1606 {
1607 started = true;
1608 }
1609
1610 text.Append(textBlock[j].Text);
1611 }
1612 }
1613 }
1614 }
1615
1616 return text.ToString();
1617 }
1618
1619 /// <summary>
1620 /// Attempts to unlock the document with the supplied password.
1621 /// </summary>
1622 /// <param name="password">The user or owner password to use to unlock the document.</param>
1623 /// <returns><see langword="true"/>If the document was successfully unlocked (or if it was never locked to begin with), <see langword="false"/> if the password was incorrect and the document is still locked.</returns>
1624 /// <remarks>This method can be used both to unlock an encrypted document and to check whether the supplied owner password is correct.</remarks>
1625 public bool TryUnlock(string password)
1626 {
1627 return TryUnlock(password, out _);
1628 }
1629
1630 /// <summary>
1631 /// Attempts to unlock the document with the supplied password.
1632 /// </summary>
1633 /// <param name="password">The user or owner password to use to unlock the document.</param>
1634 /// <param name="passwordType">If the method returns true, this can be used to determine whether the supplied password was the user password or the owner password. If the method returns <see langword="false"/>,
1635 /// this can be used to determine whether a user password and/or an owner password are required.</param>
1636 /// <returns><see langword="true"/>If the document was successfully unlocked (or if it was never locked to begin with), <see langword="false"/> if the password was incorrect and the document is still locked.</returns>
1637 /// <remarks>This method can be used both to unlock an encrypted document and to check whether the supplied owner password is correct.</remarks>
1638 public bool TryUnlock(string password, out PasswordTypes passwordType)
1639 {
1640 int result = NativeMethods.UnlockWithPassword(this.OwnerContext.NativeContext, this.NativeDocument, password);
1641
1642 int pt = 0;
1643
1644 switch (result)
1645 {
1646 case 0:
1647 pt = 0;
1648
1649 if (this.EncryptionState == EncryptionState.Encrypted)
1650 {
1651 pt |= 1;
1652 }
1653
1654 if (this.RestrictionState == RestrictionState.Restricted)
1655 {
1656 pt |= 2;
1657 }
1658
1659 passwordType = (PasswordTypes)pt;
1660 return !(this.EncryptionState == EncryptionState.Encrypted || this.RestrictionState == RestrictionState.Restricted);
1661 case 1:
1662 passwordType = PasswordTypes.None;
1663 return true;
1664 case 2:
1665 if (this.EncryptionState == EncryptionState.Encrypted)
1666 {
1667 this.EncryptionState = EncryptionState.Unlocked;
1668 }
1669 passwordType = PasswordTypes.User;
1670 return true;
1671 case 4:
1672 if (this.RestrictionState == RestrictionState.Restricted)
1673 {
1674 this.RestrictionState = RestrictionState.Unlocked;
1675 }
1676 passwordType = PasswordTypes.Owner;
1677 return true;
1678 case 6:
1679 pt = 0;
1680
1681 if (this.EncryptionState == EncryptionState.Encrypted)
1682 {
1683 this.EncryptionState = EncryptionState.Unlocked;
1684 pt |= 1;
1685 }
1686
1687 if (this.RestrictionState == RestrictionState.Restricted)
1688 {
1689 this.RestrictionState = RestrictionState.Unlocked;
1690 pt |= 2;
1691 }
1692 passwordType = (PasswordTypes)pt;
1693 return true;
1694 default:
1695 throw new ArgumentOutOfRangeException("Unexpected return value when unlocking the document: " + result.ToString());
1696 }
1697 }
1698
1699
1700 private bool disposedValue;
1701
1702 ///<inheritdoc/>
1703 protected virtual void Dispose(bool disposing)
1704 {
1705 if (!disposedValue)
1706 {
1707 if (disposing)
1708 {
1709 Pages.Dispose();
1710 foreach (MuPDFDisplayList list in DisplayLists)
1711 {
1712 list?.Dispose();
1713 }
1714 DataHandle?.Free();
1715 DataHolder?.Dispose();
1716 }
1717
1718 NativeMethods.DisposeDocument(OwnerContext.NativeContext, NativeDocument);
1719
1720 if (NativeStream != IntPtr.Zero)
1721 {
1722 NativeMethods.DisposeStream(OwnerContext.NativeContext, NativeStream);
1723 }
1724
1725 disposedValue = true;
1726 }
1727 }
1728
1729 ///<inheritdoc/>
1730 ~MuPDFDocument()
1731 {
1732 Dispose(disposing: false);
1733 }
1734
1735 ///<inheritdoc/>
1736 public void Dispose()
1737 {
1738 Dispose(disposing: true);
1739 GC.SuppressFinalize(this);
1740 }
1741 }
1742}
An IDisposable wrapper around an IntPtr that frees the allocated memory when it is disposed.
Definition: MuPDF.cs:422
The exception that is thrown when an attempt is made to render an encrypted document without supplyin...
Definition: MuPDF.cs:510
A wrapper around a MuPDF context object, which contains the exception stack and the resource cache st...
Definition: MuPDFContext.cs:26
A wrapper over a MuPDF document object, which contains possibly multiple pages.
void SaveImage(int pageNumber, double zoom, PixelFormats pixelFormat, string fileName, RasterOutputFileTypes fileType, bool includeAnnotations=true)
Save a page to an image file in the specified format.
byte[] Render(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, bool includeAnnotations=true)
Render (part of) a page to an array of bytes.
static int GetRenderedSize(Rectangle region, double zoom, PixelFormats pixelFormat)
Determine how many bytes will be necessary to render the specified region in page units at the specif...
void Layout(float width, float height, float em)
Sets the document layout for reflowable document types (e.g., HTML, MOBI). Does not have any effect f...
int GetRenderedSize(int pageNumber, double zoom, PixelFormats pixelFormat)
Determine how many bytes will be necessary to render the specified page at the specified zoom level,...
void ClearCache()
Discard all the display lists that have been loaded from the document, possibly freeing some memory i...
void WriteImageAsJPEG(int pageNumber, Rectangle region, double zoom, Stream outputStream, int quality, bool includeAnnotations=true)
Write (part of) a page to an image stream in JPEG format, with the specified quality.
void SaveImageAsJPEG(int pageNumber, Rectangle region, double zoom, string fileName, int quality, bool includeAnnotations=true)
Save (part of) a page to an image file in JPEG format, with the specified quality.
bool TryUnlock(string password)
Attempts to unlock the document with the supplied password.
MuPDFStructuredTextPage GetStructuredTextPage(int pageNumber, TesseractLanguage ocrLanguage, bool includeAnnotations=true, CancellationToken cancellationToken=default, IProgress< OCRProgressInfo > progress=null)
Creates a new MuPDFStructuredTextPage from the specified page, using optical character recognition (O...
Span< byte > Render(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, out IDisposable disposable, bool includeAnnotations=true)
Render (part of) a page to a Span<byte>.
void Render(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, IntPtr destination, bool includeAnnotations=true)
Render (part of) a page to the specified destination.
bool ClipToPageBounds
Defines whether the images resulting from rendering operations should be clipped to the page boundari...
void SaveImage(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, string fileName, RasterOutputFileTypes fileType, bool includeAnnotations=true)
Save (part of) a page to an image file in the specified format.
MuPDFDocument(MuPDFContext context, ref MemoryStream data, InputFileTypes fileType)
Create a new MuPDFDocument from a MemoryStream.
MuPDFMultiThreadedPageRenderer GetMultiThreadedRenderer(int pageNumber, int threadCount, bool includeAnnotations=true)
Create a new MuPDFMultiThreadedPageRenderer that renders the specified page with the specified number...
MuPDFDocument(MuPDFContext context, string fileName)
Create a new MuPDFDocument from a file.
static void CreateDocument(MuPDFContext context, string fileName, DocumentOutputFileTypes fileType, bool includeAnnotations=true, params(MuPDFPage page, Rectangle region, float zoom)[] pages)
Create a new document containing the specified (parts of) pages from other documents.
MuPDFStructuredTextPage GetStructuredTextPage(int pageNumber, bool includeAnnotations=true)
Creates a new MuPDFStructuredTextPage from the specified page. This contains information about the te...
async Task< string > ExtractTextAsync(TesseractLanguage ocrLanguage, string separator=null, bool includeAnnotations=true, CancellationToken cancellationToken=default, IProgress< OCRProgressInfo > progress=null)
Extracts all the text from the document and returns it as a string, using optical character recogniti...
string ExtractText(string separator=null, bool includeAnnotations=true)
Extracts all the text from the document and returns it as a string. The reading order is taken from t...
void SaveImageAsJPEG(int pageNumber, double zoom, string fileName, int quality, bool includeAnnotations=true)
Save a page to an image file in JPEG format, with the specified quality.
void LayoutSinglePage(float width, float em)
Sets the document layout for reflowable document types (e.g., HTML, MOBI), so that the document is re...
byte[] Render(int pageNumber, double zoom, PixelFormats pixelFormat, bool includeAnnotations=true)
Render a page to an array of bytes.
void WriteImageAsJPEG(int pageNumber, double zoom, Stream outputStream, int quality, bool includeAnnotations=true)
Write a page to an image stream in JPEG format, with the specified quality.
MuPDFDocument(MuPDFContext context, byte[] data, InputFileTypes fileType)
Create a new MuPDFDocument from an array of bytes.
MuPDFDocument(MuPDFContext context, IntPtr dataAddress, long dataLength, InputFileTypes fileType, ref IDisposable dataHolder)
Create a new MuPDFDocument from data bytes accessible through the specified pointer.
void WriteImage(int pageNumber, double zoom, PixelFormats pixelFormat, Stream outputStream, RasterOutputFileTypes fileType, bool includeAnnotations=true)
Write a page to an image stream in the specified format.
Span< byte > Render(int pageNumber, double zoom, PixelFormats pixelFormat, out IDisposable disposable, bool includeAnnotations=true)
Render a page to a Span<byte>.
async Task< MuPDFStructuredTextPage > GetStructuredTextPageAsync(int pageNumber, TesseractLanguage ocrLanguage, bool includeAnnotations=true, CancellationToken cancellationToken=default, IProgress< OCRProgressInfo > progress=null)
Creates a new MuPDFStructuredTextPage from the specified page, using optical character recognition (O...
void WriteImage(int pageNumber, Rectangle region, double zoom, PixelFormats pixelFormat, Stream outputStream, RasterOutputFileTypes fileType, bool includeAnnotations=true)
Write (part of) a page to an image stream in the specified format.
MuPDFDocument(MuPDFContext context, IntPtr dataAddress, long dataLength, InputFileTypes fileType)
Create a new MuPDFDocument from data bytes accessible through the specified pointer.
MuPDFPageCollection Pages
The pages contained in the document.
void Render(int pageNumber, double zoom, PixelFormats pixelFormat, IntPtr destination, bool includeAnnotations=true)
Render a page to the specified destination.
bool TryUnlock(string password, out PasswordTypes passwordType)
Attempts to unlock the document with the supplied password.
string ExtractText(TesseractLanguage ocrLanguage, string separator=null, bool includeAnnotations=true)
Extracts all the text from the document and returns it as a string, using optical character recogniti...
static void CreateDocument(MuPDFContext context, string fileName, DocumentOutputFileTypes fileType, bool includeAnnotations=true, params MuPDFPage[] pages)
Create a new document containing the specified pages from other documents.
DocumentRestrictions Restrictions
Describes the operations that are restricted on the document. This is not actually enforced by the li...
The exception that is thrown when a MuPDF operation fails.
Definition: MuPDF.cs:494
A class that holds the necessary resources to render a page of a MuPDF document using multiple thread...
A lazy collection of MuPDFPages. Each page is loaded from the document as it is requested for the fir...
Definition: MuPDFPage.cs:132
int Count
The number of pages in the collection.
Definition: MuPDFPage.cs:156
A wrapper over a MuPDF page object, which contains information about the page's boundaries.
Definition: MuPDFPage.cs:28
Represents a structured text block containing text or an image.
abstract int Count
The number of lines in the block.
Represents a structured representation of the text contained in a page.
Represents a language used by Tesseract OCR.
DocumentOutputFileTypes
Document file types supported in output by the library.
Definition: MuPDF.cs:230
InputFileTypes
File types supported in input by the library.
Definition: MuPDF.cs:123
EncryptionState
Possible document encryption states.
Definition: MuPDF.cs:277
RestrictionState
Possible document restriction states.
Definition: MuPDF.cs:298
ExitCodes
Exit codes returned by native methods describing various errors that can occur.
Definition: MuPDF.cs:32
DocumentRestrictions
Document restrictions.
Definition: MuPDF.cs:319
PixelFormats
Pixel formats supported by the library.
Definition: MuPDF.cs:251
PasswordTypes
Password types.
Definition: MuPDF.cs:350
RasterOutputFileTypes
Raster image file types supported in output by the library.
Definition: MuPDF.cs:199
Represents a rectangle.
Definition: Rectangles.cs:327
RoundedRectangle Round()
Round the rectangle's coordinates to the closest integers.
Definition: Rectangles.cs:392
float Width
The width of the rectangle.
Definition: Rectangles.cs:351
float Height
The height of the rectangle.
Definition: Rectangles.cs:356
float Y0
The top coordinate of the rectangle.
Definition: Rectangles.cs:336
float X1
The right coordinate of the rectangle.
Definition: Rectangles.cs:341
float X0
The left coordinate of the rectangle.
Definition: Rectangles.cs:331
float Y1
The bottom coordinate of the rectangle.
Definition: Rectangles.cs:346
Represents a rectangle using only integer numbers.
Definition: Rectangles.cs:495
int Width
The width of the rectangle.
Definition: Rectangles.cs:519
int Height
The height of the rectangle.
Definition: Rectangles.cs:524
Represents the size of a rectangle using only integer numbers.
Definition: Rectangles.cs:182