Compare commits

...

183 Commits

Author SHA1 Message Date
MarcinZiabek bd577b9609 Implemented custom tree view with proper indendation 2022-07-18 10:47:07 +02:00
MarcinZiabek 100afbdc32 Prototype implementation 2022-07-12 15:21:50 +02:00
MarcinZiabek 2a0b133185 Fixed incorrect file name 2022-06-23 20:32:36 +02:00
Marcin Ziąbek 6058dac524 Merge pull request #249 from wieslawsoltes/CreateGlobalJson
Add global.json
2022-06-23 00:41:36 +02:00
Marcin Ziąbek 25b8f62245 Merge pull request #248 from wieslawsoltes/SetApplicationName
Set application name
2022-06-23 00:40:51 +02:00
MarcinZiabek 2c31f3d5b5 2022.6.2 2022-06-23 00:16:13 +02:00
Wiesław Šoltés f7cd89ccf6 Add global.json 2022-06-15 21:55:44 +02:00
Wiesław Šoltés 6914a9063a Set application name 2022-06-15 21:53:34 +02:00
MarcinZiabek b87e8d5d4e Improved exception messages 2022-06-14 17:53:16 +02:00
MarcinZiabek d740d67a64 2022.6.1 2022-06-13 00:17:32 +02:00
MarcinZiabek a14e9ca61f 2022.6 2022-06-12 23:48:38 +02:00
MarcinZiabek 44b3ff8d0d Merge branch 'text-shaping-new' 2022-06-08 02:46:54 +02:00
Marcin Ziąbek df6b88e14a Update readme.md 2022-06-08 02:10:52 +02:00
MarcinZiabek d4103797aa Improved exception message when SkiaSharp throws the TypeInitializationException 2022-06-08 02:01:58 +02:00
MarcinZiabek d894914e7e Improved Previewer stability 2022-06-08 00:09:59 +02:00
MarcinZiabek 90d1ce7af7 2022.6.0-prerelease 2022-05-31 00:52:49 +02:00
MarcinZiabek 5fa32a06cc Text: fixed corner case 2022-05-26 00:42:50 +02:00
MarcinZiabek acdf1ed1aa Stability improvement 2022-05-25 00:21:50 +02:00
MarcinZiabek a0b32e5ecf Fixed Row.AutoItem width calculation 2022-05-25 00:21:50 +02:00
MarcinZiabek 539dc83303 Fixed Row.AutoItem width calculation 2022-05-24 23:50:53 +02:00
MarcinZiabek d97fa2cef1 Minor optimization of SkFont creation 2022-05-23 01:18:21 +02:00
MarcinZiabek 6d122c784d Implemented working text shaping 2022-05-22 20:25:11 +02:00
MarcinZiabek 28743a81d1 Fixed typo 2022-05-21 14:35:13 +02:00
MarcinZiabek 32b94cea43 Updated sponsorship 2022-05-12 00:40:29 +02:00
Marcin Ziąbek 0ca55a91e0 Update readme.md 2022-05-10 21:48:39 +02:00
MarcinZiabek 09e6b5253f Fixed class names 2022-05-10 12:43:13 +02:00
MarcinZiabek bacb136a3f Benchmark: grid element 2022-05-10 12:42:49 +02:00
MarcinZiabek b7bc640e31 Added new example to dynamic component: footer with alternating position 2022-05-09 18:18:06 +02:00
MarcinZiabek 6e6f884a5f Updated sponsorship section 2022-05-09 16:33:12 +02:00
MarcinZiabek b47a8ac409 Added more examples 2022-05-09 16:32:59 +02:00
MarcinZiabek e51082400c Dynamic Component: added more examples 2022-05-09 14:07:50 +02:00
MarcinZiabek 52cfa3ab7e Added note regarding JetBrains OSS Power Ups 2022-05-07 22:18:14 +02:00
MarcinZiabek cd181b1e2e Merge branch '2022.5' 2022-05-07 21:40:10 +02:00
Marcin Ziąbek e71601ad0c Merge pull request #208 from danielchalmers/patch-1
replace obsolete method in readme example
2022-05-07 18:46:09 +02:00
Daniel Chalmers 3041aa53cc replace obsolete method in readme example
Warning	CS0618	'PageDescriptor.Background(string)' is obsolete: 'This element has been renamed since version 2022.3. Please use the PageColor method.'
2022-05-06 19:37:17 -05:00
MarcinZiabek 0ebf2833af Updated release notes 2022-05-06 21:17:16 +02:00
MarcinZiabek 25e6d9fbca Adjusted library versions 2022-05-06 21:09:16 +02:00
Marcin Ziąbek f80149792d Merge pull request #178 from jnyrup/Dispose
Explicit disposal of disposables
2022-05-06 20:59:35 +02:00
Marcin Ziąbek 2beee1d22e Update PreviewerService.cs 2022-05-06 20:59:06 +02:00
MarcinZiabek 1af7c0a144 Adjusted code related to subscript/superscript 2022-05-06 20:55:18 +02:00
Marcin Ziąbek eeca67e5bc Merge pull request #179 from Bebo-Maker/main
Implement subscript/superscript text
2022-05-06 00:09:31 +02:00
MarcinZiabek 4da3ef5dfa Fixed breaking long words 2022-05-05 23:52:17 +02:00
MarcinZiabek f3d4151273 Improved performance of the table rendering algorithm (square to linear complexity) 2022-05-05 23:20:23 +02:00
Marcin Ziąbek 9a7e397db2 Merge pull request #197 from knoxyz/patch-2
Replaced the obsolete methods from 2022.3 (FontType, Color and Size)
2022-05-05 18:23:11 +02:00
Marcin Ziąbek d81f21e658 Merge pull request #196 from knoxyz/patch-1
Update <TargetFramework> from 3.1 to net6.0
2022-05-05 18:11:21 +02:00
Marcin Ziąbek 78909c6edf Merge pull request #199 from knoxyz/patch-4
Replaced the obsolete methods from 2022.3 (InternalLink and Color)
2022-05-05 17:48:28 +02:00
Marcin Ziąbek a2d81b6adf Merge pull request #200 from knoxyz/patch-3
Replaced the obsolete methods from 2022.3 (ExternalLink and Location)
2022-05-05 17:47:40 +02:00
Marcin Ziąbek 4a761c023f Merge pull request #201 from simusr2/main
Replaced PageEnd with PageStart in PageNumberWithinSection
2022-05-05 17:46:30 +02:00
Simone Anile 24bbd06dff Replaced PageEnd with PageStart in PageNumberWithinSection 2022-04-29 15:19:58 +02:00
knoxyz 44bd646bab Replaced the obsolete methods from 2022.3 (InternalLink and Color) 2022-04-29 11:36:28 +02:00
knoxyz 5f0291b2c4 Replaced the obsolete methods from 2022.3 (ExternalLink and Location) 2022-04-29 11:32:20 +02:00
knoxyz 1b91015d8f Replaced the obsolete methods from 2022.3 (FontType, Color and Size) 2022-04-28 16:21:56 +02:00
knoxyz 2b6444463d Update <TargetFramework> from 3.1 to net6.0 2022-04-28 16:13:44 +02:00
MarcinZiabek a2629575e8 Dynamic: simplified container composition process 2022-04-27 09:50:17 +02:00
MarcinZiabek c8824021cc Dynamic: generalized dynamic element size measurement 2022-04-27 09:24:44 +02:00
MarcinZiabek e1101d870d Dynamic: improve state management 2022-04-27 06:56:13 +02:00
MarcinZiabek 152304b467 Dynamic: improved TextStyle handling 2022-04-26 01:11:40 +02:00
MarcinZiabek 8a1be90c4f Ported dynamic component implementation prototype 2022-04-26 00:36:50 +02:00
MarcinZiabek bc8a9e5dc5 Fixed mutating text style for page-related text items 2022-04-23 23:13:38 +02:00
Marcin Ziąbek e54e09de15 Merge pull request #185 from mikasius/main
Renamed logo.png
2022-04-23 23:03:37 +02:00
Sergey Metlenkin c49a424f87 Renamed logo.png
Otherwise solution won't build
2022-04-22 10:59:12 +07:00
Bebo-Maker c63b0658f3 Ignore FontPosition when calculating font metrics. 2022-04-12 19:14:00 +02:00
Bebo-Maker 82cd1d7835 Add example for superscript/subscript. 2022-04-12 18:49:34 +02:00
Bebo-Maker 3ffab6e933 Implement superscript and subscript. 2022-04-12 18:48:28 +02:00
Marcin Ziąbek e4d55e0298 Merge pull request #177 from jnyrup/ArgumentException
Use `paramName` overload of `ArgumentException`
2022-04-11 22:03:40 +02:00
Jonas Nyrup a6f4fd7f49 explicit disposal 2022-04-11 21:36:50 +02:00
Jonas Nyrup 048a4748b2 Use paramName overload of ArgumentException 2022-04-11 21:33:33 +02:00
Marcin Ziąbek 8cc2fb8696 Merge pull request #169 from fredericoregateiro/AvaloniaCompiledBindings
Enable compiled bindings on Previewer
2022-04-08 22:17:41 +02:00
MarcinZiabek 8e7b528cd9 Updated install command in readme 2022-04-08 22:15:12 +02:00
MarcinZiabek 26193313b8 Previewer: hide "Show in default browser" button when no document available 2022-04-08 22:14:19 +02:00
MarcinZiabek 555825e8be Previewer: throwing exception when cannot connect with frontend 2022-04-08 22:14:19 +02:00
Frederico Regateiro ddde8e7517 enable compiled bindings on PreviewerWindow 2022-04-06 14:08:53 +01:00
Marcin Ziąbek d2c82b5988 Update readme.md 2022-04-05 14:54:33 +02:00
Marcin Ziąbek d82aae95b5 Update readme.md 2022-04-05 14:34:00 +02:00
Marcin Ziąbek 5c11a2ab6a Update readme.md 2022-04-05 14:30:51 +02:00
MarcinZiabek 03e3a80fd9 Fixed logo resource for QuestPDF.Previwer 2022-04-04 23:26:16 +02:00
MarcinZiabek 4692c5ce62 Fixed QuestPDF.Previewer build 2022-04-04 23:24:02 +02:00
MarcinZiabek 919f916219 2022.4 release 2022-04-04 23:20:22 +02:00
MarcinZiabek 7a32f774a1 Fixed spelling 2022-04-04 00:38:58 +02:00
MarcinZiabek fba71e6392 Previewer: stop debugging session when previewer is closed 2022-04-04 00:19:36 +02:00
MarcinZiabek e27585a504 Fixed previewer: first document refresh sometimes does not work 2022-04-02 00:23:58 +02:00
MarcinZiabek 9be497ee41 Fixed automatically starting previewer application on custom port 2022-04-01 19:26:54 +02:00
MarcinZiabek 0fc0db55d2 Better button style path in previewer 2022-03-31 23:14:00 +02:00
MarcinZiabek 9e7a1ed49d Previewer improvements 2022-03-31 23:11:34 +02:00
MarcinZiabek c137bcfcde Improved look and feel 2022-03-31 18:33:34 +02:00
MarcinZiabek b80b3414d8 Added http-based previewer 2022-03-31 15:44:28 +02:00
MarcinZiabek 5f7cee3420 Removed old previewer implementation 2022-03-30 23:38:51 +02:00
MarcinZiabek 435c42ab9e Changed previewer communication layer to http 2022-03-30 23:37:37 +02:00
MarcinZiabek 7b4923b45d Merge remote-tracking branch 'origin/previewer' 2022-03-27 20:15:02 +02:00
MarcinZiabek 8732b7e50b Renaming 2022-03-27 20:12:22 +02:00
MarcinZiabek 6b31322f92 Fixed preview button labels 2022-03-27 17:29:50 +02:00
MarcinZiabek 6155d21207 Previewer: added buttons for documentation and project sponsoring 2022-03-27 14:49:50 +02:00
Marcin Ziąbek a0909eb057 Create FUNDING.yml 2022-03-26 21:06:44 +01:00
MarcinZiabek 11ff53946d Introduced new line breaking algorithm: break anywhere 2022-03-26 03:34:31 +01:00
MarcinZiabek 0711bca1e1 Improved respecting spaces at the beginning and end of text block, fixed breaking long words that do not fit in line 2022-03-26 02:58:55 +01:00
MarcinZiabek 297ca48c70 Code cleanup 2022-03-24 23:09:09 +01:00
Marcin Ziąbek d2601e9e5a Merge pull request #157 from Bebo-Maker/previewer
Previewer - ViewModel for PreviewerWindow
2022-03-24 22:20:28 +01:00
Bebo-Maker 421a926780 Fix vertical scroll bar visibility. 2022-03-24 22:05:49 +01:00
Bebo-Maker 307759d1b3 Remove StyledProperties and rework bindings. 2022-03-24 22:01:10 +01:00
Bebo-Maker 00bcb2c25b Add ViewModel for PreviewerWindow. 2022-03-24 21:59:55 +01:00
MarcinZiabek e430484e85 Fixed zooming out 2022-03-24 13:54:38 +01:00
MarcinZiabek d24d887a3a Previewer: simplified translate operations 2022-03-24 13:35:02 +01:00
MarcinZiabek 404914ea7e Previewer: added button to show generated PDF 2022-03-24 01:02:57 +01:00
Marcin Ziąbek 545b76fd40 Merge pull request #155 from Bebo-Maker/previewer
Previewer - Fix multiple small issues
2022-03-24 00:04:34 +01:00
Marcin Ziąbek c801894ec4 Merge branch 'previewer' into previewer 2022-03-24 00:04:28 +01:00
MarcinZiabek c21744e7de Fixed scrolling math 2022-03-23 23:31:27 +01:00
Bebo-Maker 734d116bfe Fix scroll viewer visibility. 2022-03-23 12:58:58 +01:00
Bebo-Maker 0abb67b748 Unregister HotReload handler when window is closed 2022-03-23 09:22:46 +01:00
Bebo-Maker 828bb86737 Fix MaxWidth calculation when pages are empty. 2022-03-23 08:35:53 +01:00
Bebo-Maker 040c47e017 Fix issue when showing previewer multiple times.
Previously when calling ShowPreviewer  in the same process multiple times lead to an Exception inside Avalonia.
2022-03-23 08:35:28 +01:00
MarcinZiabek ff95fe287a Added scrollbar interactions 2022-03-23 01:10:15 +01:00
MarcinZiabek 47e9a49fe5 Fixed scroll, more code refactoring 2022-03-21 01:33:33 +01:00
MarcinZiabek b2826a06b8 Code refactorization + cleanup 2022-03-21 01:16:26 +01:00
MarcinZiabek 321cba32ce Implemented basic canvas interactions 2022-03-21 01:02:53 +01:00
MarcinZiabek 26fdbb714d Simplified rendering operation 2022-03-20 01:53:51 +01:00
Marcin Ziąbek f0cec8988a Merge pull request #147 from Bebo-Maker/document-previewer
Document previewer
2022-03-20 00:48:50 +01:00
Bebo-Maker ea2102fe4c WIP Implemented virtualization of pages.
Render pages into seperate SKPictures
2022-03-19 17:26:58 +01:00
Bebo-Maker 6cca3ed057 Refactor custom drawing operation. 2022-03-17 22:32:37 +01:00
Bebo-Maker 9afe4ec104 Add button for generating pdf in previewer. 2022-03-17 20:03:20 +01:00
Bebo-Maker 846679007f Enable dark mode for previewer and cleanup UI. 2022-03-17 19:35:46 +01:00
Bebo-Maker 0946607154 Add table to example. 2022-03-17 12:05:27 +01:00
Bebo-Maker d5f86e0d70 Fixed rendering issue where content was clipped. 2022-03-17 11:44:40 +01:00
Bebo-Maker 346513ee85 Optimize document rendering.
We dont need to render the document two times anymore.
2022-03-17 10:34:00 +01:00
Bebo-Maker d18ee5f282 Allow intersection after first RenderPass. 2022-03-17 10:32:41 +01:00
Bebo-Maker a85bc0abc1 Add example for using document previewer. 2022-03-16 22:54:03 +01:00
Bebo-Maker 82e39df572 Enable SKPaint.IsAntialias in FontManager. 2022-03-16 22:48:30 +01:00
Bebo-Maker 1311b6db32 Add PreviewerApp and provide extensions for displaying previewer. 2022-03-16 22:48:07 +01:00
Bebo-Maker b5645bfbd5 Implement document preview. 2022-03-16 22:47:05 +01:00
Bebo-Maker 8217b3b54a Implement HotReloadManager. 2022-03-16 22:46:21 +01:00
Bebo-Maker 726759f47a Add size tracking canvas. 2022-03-16 22:45:57 +01:00
Bebo-Maker bbd9c3379e Change access modifier of RenderDocument to internal. 2022-03-16 22:45:39 +01:00
Bebo-Maker 697c5e238d Add InternalsVisibleTo for QuestPDF.Previewer in QuestPDF AssemblyInfo. 2022-03-16 22:45:13 +01:00
Bebo-Maker 21a92be87e Add .editorconfig . 2022-03-16 22:44:45 +01:00
Bebo-Maker b0cca1ff48 Add QuestPDF.Previewer and QuestPDF.Previewer.Examples 2022-03-16 22:42:16 +01:00
Marcin Ziąbek 78c5049eda Update readme.md 2022-03-15 23:23:18 +01:00
MarcinZiabek 3d0ca07b43 Release 2022.3.1 2022-03-15 13:06:09 +01:00
MarcinZiabek a67fa754af Fixed NullReferenceException when the text is null 2022-03-15 11:55:59 +01:00
MarcinZiabek 208235ad1e Updated description 2022-03-14 13:59:26 +01:00
Marcin Ziąbek f24416474a Update readme.md 2022-03-14 13:49:21 +01:00
MarcinZiabek 01c2e8d3f5 Updated documentation. Release 2022.03 2022-03-14 13:17:33 +01:00
MarcinZiabek b3cef238ec Improved page color API 2022-03-13 22:47:17 +01:00
Marcin Ziąbek aede62c135 Update readme.md 2022-03-12 23:04:11 +01:00
MarcinZiabek db9db95dcc Fixed build 2022-03-12 19:20:01 +01:00
MarcinZiabek 92263685d7 Merge branch '2022.03'
# Conflicts:
#	QuestPDF.Examples/TextExamples.cs
#	QuestPDF/QuestPDF.csproj
2022-03-12 19:17:38 +01:00
MarcinZiabek ba4be521aa 2022.2.7 2022-03-12 19:01:39 +01:00
Marcin Ziąbek f6967dfe8b Update readme.md 2022-03-12 16:42:26 +01:00
MarcinZiabek ff3dfe7831 Fixed logo size 2022-03-12 16:39:33 +01:00
MarcinZiabek 5a1990d857 Added more shields 2022-03-12 16:38:03 +01:00
MarcinZiabek 1b9b164583 DefaultTextStyle API improvements 2022-03-12 16:11:03 +01:00
Marcin Ziąbek 9c64a6bc8d Merge pull request #128 from loxsmoke/main
Add nuget badges to readme.md file
2022-03-12 16:06:04 +01:00
MarcinZiabek 5402e5fedc Fixed text rendering issues 2022-03-12 12:17:00 +01:00
MarcinZiabek 2c14f91ebe Added support for background and foreground in page API 2022-03-11 18:04:30 +01:00
MarcinZiabek b533a32b61 Text API naming adjustments 2022-03-11 13:23:36 +01:00
MarcinZiabek 262bd3c182 Improved backwards compatibility 2022-03-09 22:13:51 +01:00
MarcinZiabek fdf8e3eb1a Fixed text rendering corner case: measuring span with space at the end of the line may overflow available space 2022-03-08 18:33:34 +01:00
MarcinZiabek fcff989ab2 Improved text API 2022-03-08 17:27:30 +01:00
MarcinZiabek 054e7a5f20 Added minimal API example 2022-02-28 15:41:10 +01:00
MarcinZiabek a92cc25fb1 Improved font matching and exception message. Refactored tests. 2022-02-27 15:19:50 +01:00
MarcinZiabek 8db283d559 Code refactorization + added support for multi typeface font types 2022-02-25 23:02:42 +01:00
LoxSmoke 456c7a735b Update readme.md 2022-02-24 19:55:36 -08:00
LoxSmoke 5bf2a9a3d3 Nuget badges in readme file 2022-02-24 19:53:51 -08:00
Marcin Ziąbek 8a64abaac2 Merge pull request #106 from SvizelPritula/main
FontManager: Add support for multiple variants of font families registered at runtime
2022-02-21 22:26:51 +01:00
MarcinZiabek 68df390d0e Element renaming + custom text format 2022-02-21 00:57:53 +01:00
MarcinZiabek d73c694452 Added minimal API 2022-02-20 16:17:43 +01:00
MarcinZiabek 69e268733c 2022.2.5 2022-02-18 23:31:53 +01:00
MarcinZiabek 657bbe2aac 2022.2.4 2022-02-18 21:48:57 +01:00
MarcinZiabek 4479a49ad0 Improved support for page sections 2022-02-16 15:34:54 +01:00
MarcinZiabek 63288dfb60 Updated examples 2022-02-07 22:29:59 +01:00
MarcinZiabek fc325d8b76 2022.2.3 2022-02-07 22:25:48 +01:00
Marcin Ziąbek 5ffeaa3075 Merge pull request #119 from B-wareBS/features/unit-conversion-fix
Fixed an issue with unit conversion.
2022-02-07 22:04:55 +01:00
Chris de Jong-Pobedinskiy fe431fbc12 Fixed an issue with unit conversion.
Fixed an issue where unit conversions from mm to pt would be off by a
factor of 100.

This fixes #118
2022-02-07 13:56:20 +01:00
Marcin Ziąbek e0de408821 2022.2.2 Fixed regression: exception when Column/Row have no items 2022-02-01 23:39:43 +01:00
Marcin Ziąbek 8ce6afef5a Simplified readme 2022-02-01 11:14:38 +01:00
Marcin Ziąbek 1c70b20028 Added examples for execution order 2022-01-31 00:15:10 +01:00
Marcin Ziąbek 3aef4ec4c9 2022.2.1 2022-01-30 23:46:11 +01:00
Marcin Ziąbek 3649607bd9 Merge remote-tracking branch 'origin/2022.02'
# Conflicts:
#	QuestPDF.Examples/RowExamples.cs
#	QuestPDF.UnitTests/RowTests.cs
#	QuestPDF/Elements/Row.cs
#	QuestPDF/Fluent/RowExtensions.cs
#	QuestPDF/QuestPDF.csproj
#	QuestPDF/Resources/ReleaseNotes.txt
2022-01-30 23:44:20 +01:00
Marcin Ziąbek 9a6b11ab85 Updated examples and readme 2022-01-30 22:52:56 +01:00
Benjamin Swart 78eef45ce2 FontManager: Add support for multiple variants of font families registered at runtime 2022-01-24 15:13:41 +01:00
Marcin Ziąbek ba409a8712 Create codeql-analysis.yml 2022-01-21 03:11:22 +01:00
Marcin Ziąbek e3ce194fd1 Simple SVG example 2022-01-16 17:03:01 +01:00
Marcin Ziąbek 9b892c86ed 2022.1.0 2022-01-10 02:43:58 +01:00
Marcin Ziąbek c9833d410d 2022.1.0-beta6 2022-01-09 03:08:18 +01:00
Marcin Ziąbek 763fdfac0f Row: fixed paging behavior, Table: improved paging behavior for already rendered elements 2022-01-07 13:15:27 +01:00
154 changed files with 6016 additions and 1050 deletions
+131
View File
@@ -0,0 +1,131 @@
# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\GithubRepos\QuestPDF codebase based on best match to current usage at 16.03.2022
# You can modify the rules from these initially generated values to suit your own policies
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
[*.cs]
#Core editorconfig formatting - indentation
#use soft tabs (spaces) for indentation
indent_style = space
#Formatting - new line options
#place catch statements on a new line
csharp_new_line_before_catch = true
#place else statements on a new line
csharp_new_line_before_else = true
#require members of anonymous types to be on separate lines
csharp_new_line_before_members_in_anonymous_types = true
#require members of object intializers to be on separate lines
csharp_new_line_before_members_in_object_initializers = true
#require braces to be on a new line for object_collection_array_initializers, methods, anonymous_types, control_blocks, types, and lambdas (also known as "Allman" style)
csharp_new_line_before_open_brace =all
#Formatting - organize using options
#sort System.* using directives alphabetically, and place them before other usings
dotnet_sort_system_directives_first = true
#Formatting - spacing options
#require NO space between a cast and the value
csharp_space_after_cast = false
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true
#remove space within empty argument list parentheses
csharp_space_between_method_call_empty_parameter_list_parentheses = false
#remove space between method call name and opening parenthesis
csharp_space_between_method_call_name_and_opening_parenthesis = false
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
csharp_space_between_method_call_parameter_list_parentheses = false
#remove space within empty parameter list parentheses for a method declaration
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
#leave code block on single line
csharp_preserve_single_line_blocks = true
#Style - Code block preferences
#prefer no curly braces if allowed
csharp_prefer_braces = false:suggestion
#Style - expression bodied member options
#prefer block bodies for constructors
csharp_style_expression_bodied_constructors = false:suggestion
#prefer block bodies for methods
csharp_style_expression_bodied_methods = false:suggestion
#prefer expression-bodied members for properties
csharp_style_expression_bodied_properties = true:suggestion
#Style - expression level options
#prefer out variables to be declared inline in the argument list of a method call when possible
csharp_style_inlined_variable_declaration = true:suggestion
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true:suggestion
#Style - Expression-level preferences
#prefer objects to be initialized using object initializers when possible
dotnet_style_object_initializer = true:suggestion
#prefer inferred anonymous type member names
dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion
#prefer inferred tuple element names
dotnet_style_prefer_inferred_tuple_names = true:suggestion
#Style - implicit and explicit types
#prefer var over explicit type in all cases, unless overridden by another code style rule
csharp_style_var_elsewhere = true:suggestion
#prefer var is used to declare variables with built-in system types such as int
csharp_style_var_for_built_in_types = true:suggestion
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
csharp_style_var_when_type_is_apparent = true:suggestion
#Style - language keyword and framework type options
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#Style - Miscellaneous preferences
#prefer local functions over anonymous functions
csharp_style_pattern_local_over_anonymous_function = true:suggestion
#Style - modifier options
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
#Style - Modifier preferences
#when this rule is set to a list of modifiers, prefer the specified ordering.
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
#Style - Pattern matching
#prefer pattern matching instead of is expression with type casts
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
#Style - qualification options
#prefer fields not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_field = false:suggestion
#prefer methods not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_method = false:suggestion
#prefer properties not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_property = false:suggestion
[*.{cs,vb}]
tab_width=4
indent_size=4
+70
View File
@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '22 16 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+1
View File
@@ -0,0 +1 @@
github: QuestPDF
+5 -3
View File
@@ -13,11 +13,11 @@ namespace QuestPDF.Examples
[Test]
public void Example()
{
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.PageSize(400, 200)
.ShowResults()
.Render(container =>
{
@@ -25,7 +25,9 @@ namespace QuestPDF.Examples
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
.Text("*QuestPDF*")
.FontFamily("Libre Barcode 39")
.FontSize(64);
});
}
}
+4 -1
View File
@@ -49,7 +49,10 @@ namespace QuestPDF.Examples
.PrimaryLayer()
.PaddingVertical(10)
.PaddingHorizontal(20)
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
.Text("Sample text")
.FontSize(16)
.FontColor(Colors.Blue.Darken2)
.SemiBold();
});
});
}
+4 -1
View File
@@ -57,7 +57,10 @@ namespace QuestPDF.Examples
column
.Item()
.PaddingBottom(10)
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
.Text("Chart example")
.FontSize(20)
.SemiBold()
.FontColor(Colors.Blue.Medium);
column
.Item()
+23 -4
View File
@@ -1,4 +1,6 @@
using NUnit.Framework;
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -20,18 +22,35 @@ namespace QuestPDF.Examples
{
container.Column(column =>
{
column.Item().Element(Block);
foreach (var i in Enumerable.Range(0, 10))
column.Item().Element(Block);
static void Block(IContainer container)
{
container
.Width(72)
.Height(3.5f)
.Height(1.5f)
.Height(3.5f, Unit.Inch)
.Height(1.5f, Unit.Inch)
.Background(Placeholders.BackgroundColor());
}
});
});
}
[Test]
public void Stability_NoItems()
{
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.Render(container =>
{
container
.Padding(25)
.Column(column => { });
});
}
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace QuestPDF.Examples
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(x => GenerateStructure(x, 16));
.Render(x => GenerateStructure(x, 12));
}
private void GenerateStructure(IContainer container, int level)
+1 -1
View File
@@ -25,7 +25,7 @@ namespace QuestPDF.Examples
page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
{
foreach (var index in Enumerable.Range(1, 100))
column.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
column.Item().Text($"Line {index}").FontColor(Placeholders.Color());
});
page.Footer().Text("Footer");
+2 -2
View File
@@ -24,7 +24,7 @@ namespace QuestPDF.Examples
page.Margin(20);
page.Size(PageSizes.A4);
page.Background(Colors.White);
page.PageColor(Colors.White);
page.Content().Column(column =>
{
@@ -38,7 +38,7 @@ namespace QuestPDF.Examples
text.Line(Placeholders.Sentence());
// this text has size 20 but also semibold and red
text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
text.Span(Placeholders.Sentence()).FontColor(Colors.Red.Medium);
});
});
});
@@ -26,8 +26,8 @@ namespace QuestPDF.Examples
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
.Column(column =>
{
column.Item().Text("Default style applies to all children", TextStyle.Default);
column.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
column.Item().Text("Default style applies to all children");
column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2);
column.Item().PaddingTop(10).Border(1).Grid(grid =>
{
@@ -43,7 +43,8 @@ namespace QuestPDF.Examples
.Height(50)
.AlignCenter()
.AlignMiddle()
.Text(i, TextStyle.Default.Size(16 + i / 4));
.Text(i)
.FontSize(16 + i / 4);
}
});
});
@@ -24,7 +24,7 @@ namespace QuestPDF.Examples
{
page.Size(PageSizes.A6);
page.Margin(5);
page.Background(Colors.White);
page.PageColor(Colors.White);
page.Header().Column(column =>
{
+106
View File
@@ -0,0 +1,106 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public struct FibonacciHeaderState
{
public int Previous { get; set; }
public int Current { get; set; }
}
public class FibonacciHeader : IDynamicComponent<FibonacciHeaderState>
{
public FibonacciHeaderState State { get; set; }
public static readonly string[] ColorsTable =
{
Colors.Red.Lighten2,
Colors.Orange.Lighten2,
Colors.Green.Lighten2,
};
public FibonacciHeader(int previous, int current)
{
State = new FibonacciHeaderState
{
Previous = previous,
Current = current
};
}
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var content = context.CreateElement(container =>
{
var colorIndex = State.Current % ColorsTable.Length;
var color = ColorsTable[colorIndex];
var ratio = (float)State.Current / State.Previous;
container
.Background(color)
.Height(50)
.AlignMiddle()
.AlignCenter()
.Text($"{State.Current} / {State.Previous} = {ratio:N5}");
});
State = new FibonacciHeaderState
{
Previous = State.Current,
Current = State.Previous + State.Current
};
return new DynamicComponentComposeResult
{
Content = content,
HasMoreContent = false
};
}
}
public static class DynamicFibonacci
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.ShowResults()
.MaxPages(100)
.ProduceImages()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.PageColor(Colors.White);
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontSize(18));
page.Header().Dynamic(new FibonacciHeader(17, 19));
page.Content().Column(column =>
{
foreach (var i in Enumerable.Range(0, 50))
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
});
page.Footer().AlignCenter().Text(text =>
{
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
});
}
}
}
@@ -0,0 +1,185 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class OrderItem
{
public string ItemName { get; set; } = Placeholders.Label();
public int Price { get; set; } = Placeholders.Random.Next(1, 11) * 10;
public int Count { get; set; } = Placeholders.Random.Next(1, 11);
}
public struct OrdersTableState
{
public int ShownItemsCount { get; set; }
}
public class OptimizedOrdersTable : IDynamicComponent<OrdersTableState>
{
private ICollection<OrderItem> Items { get; }
public OrdersTableState State { get; set; }
public OptimizedOrdersTable(ICollection<OrderItem> items)
{
Items = items;
State = new OrdersTableState
{
ShownItemsCount = 0
};
}
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var header = ComposeHeader(context);
var sampleFooter = ComposeFooter(context, Enumerable.Empty<OrderItem>());
var decorationHeight = header.Size.Height + sampleFooter.Size.Height;
var rows = GetItemsForPage(context, decorationHeight).ToList();
var footer = ComposeFooter(context, rows.Select(x => x.Item));
var content = context.CreateElement(container =>
{
container.MinimalBox().Decoration(decoration =>
{
decoration.Header().Element(header);
decoration.Content().Box().Stack(stack =>
{
foreach (var row in rows)
stack.Item().Element(row.Element);
});
decoration.Footer().Element(footer);
});
});
State = new OrdersTableState
{
ShownItemsCount = State.ShownItemsCount + rows.Count
};
return new DynamicComponentComposeResult
{
Content = content,
HasMoreContent = State.ShownItemsCount < Items.Count
};
}
private IDynamicElement ComposeHeader(DynamicContext context)
{
return context.CreateElement(element =>
{
element
.Width(context.AvailableSize.Width)
.BorderBottom(1)
.BorderColor(Colors.Grey.Darken2)
.Padding(5)
.Row(row =>
{
var textStyle = TextStyle.Default.SemiBold();
row.ConstantItem(30).Text("#", textStyle);
row.RelativeItem().Text("Item name", textStyle);
row.ConstantItem(50).AlignRight().Text("Count", textStyle);
row.ConstantItem(50).AlignRight().Text("Price", textStyle);
row.ConstantItem(50).AlignRight().Text("Total", textStyle);
});
});
}
private IDynamicElement ComposeFooter(DynamicContext context, IEnumerable<OrderItem> items)
{
var total = items.Sum(x => x.Count * x.Price);
return context.CreateElement(element =>
{
element
.Width(context.AvailableSize.Width)
.Padding(5)
.AlignRight()
.Text($"Subtotal: {total}$", TextStyle.Default.Size(14).SemiBold());
});
}
private IEnumerable<(OrderItem Item, IDynamicElement Element)> GetItemsForPage(DynamicContext context, float decorationHeight)
{
var totalHeight = decorationHeight;
foreach (var index in Enumerable.Range(State.ShownItemsCount, Items.Count - State.ShownItemsCount))
{
var item = Items.ElementAt(index);
var element = context.CreateElement(content =>
{
content
.Width(context.AvailableSize.Width)
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.Padding(5)
.Row(row =>
{
row.ConstantItem(30).Text(index + 1);
row.RelativeItem().Text(item.ItemName);
row.ConstantItem(50).AlignRight().Text(item.Count);
row.ConstantItem(50).AlignRight().Text($"{item.Price}$");
row.ConstantItem(50).AlignRight().Text($"{item.Count*item.Price}$");
});
});
var elementHeight = element.Size.Height;
if (totalHeight + elementHeight > context.AvailableSize.Height)
break;
totalHeight += elementHeight;
yield return (item, element);
}
}
}
public static class DynamicOptimizedExamples
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ShowResults()
.Render(container =>
{
var items = Enumerable.Range(0, 25).Select(x => new OrderItem()).ToList();
container
.Background(Colors.White)
.Padding(25)
.Decoration(decoration =>
{
decoration
.Header()
.PaddingBottom(5)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().FontColor(Colors.Blue.Darken2).FontSize(16));
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" of ");
text.TotalPages();
});
decoration
.Content()
.Dynamic(new OptimizedOrdersTable(items));
});
});
}
}
}
@@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class FooterWithAlternatingAlignment : IDynamicComponent<int>
{
public int State { get; set; }
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var content = context.CreateElement(element =>
{
element
.Element(x => context.PageNumber % 2 == 0 ? x.AlignLeft() : x.AlignRight())
.Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
});
return new DynamicComponentComposeResult()
{
Content = content,
HasMoreContent = false
};
}
}
public static class DynamicPageNumberLeftRightExamples
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.MaxPages(100)
.ShowResults()
.ProduceImages()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.PageColor(Colors.White);
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontSize(18));
page.Content().Column(column =>
{
foreach (var i in Enumerable.Range(0, 50))
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
});
page.Footer().Dynamic(new FooterWithAlternatingAlignment());
});
});
}
}
}
@@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ProgressHeader : IDynamicComponent<int>
{
public int State { get; set; }
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var content = context.CreateElement(container =>
{
var width = context.AvailableSize.Width * context.PageNumber / context.TotalPages;
container
.Background(Colors.Blue.Lighten2)
.Height(25)
.Width(width)
.Background(Colors.Blue.Darken1);
});
return new DynamicComponentComposeResult
{
Content = content,
HasMoreContent = false
};
}
}
public static class DynamicProgressHeader
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.ShowResults()
.MaxPages(100)
.ProduceImages()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.Margin(1, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header().Dynamic(new ProgressHeader());
page.Content().Column(column =>
{
foreach (var i in Enumerable.Range(0, 100))
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
});
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(x => x.FontSize(20));
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
});
}
}
}
@@ -0,0 +1,157 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class OrdersTable : IDynamicComponent<OrdersTableState>
{
private IList<OrderItem> Items { get; }
public OrdersTableState State { get; set; }
public OrdersTable(IList<OrderItem> items)
{
Items = items;
State = new OrdersTableState
{
ShownItemsCount = 0
};
}
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var possibleItems = Enumerable
.Range(1, Items.Count - State.ShownItemsCount)
.Select(itemsToDisplay => ComposeContent(context, itemsToDisplay))
.TakeWhile(x => x.Size.Height <= context.AvailableSize.Height)
.ToList();
State = new OrdersTableState
{
ShownItemsCount = State.ShownItemsCount + possibleItems.Count
};
return new DynamicComponentComposeResult
{
Content = possibleItems.Last(),
HasMoreContent = State.ShownItemsCount < Items.Count
};
}
private IDynamicElement ComposeContent(DynamicContext context, int itemsToDisplay)
{
var total = Items.Skip(State.ShownItemsCount).Take(itemsToDisplay).Sum(x => x.Count * x.Price);
return context.CreateElement(container =>
{
container
.MinimalBox()
.Width(context.AvailableSize.Width)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(30);
columns.RelativeColumn();
columns.ConstantColumn(50);
columns.ConstantColumn(50);
columns.ConstantColumn(50);
});
table.Header(header =>
{
header.Cell().Element(Style).Text("#");
header.Cell().Element(Style).Text("Item name");
header.Cell().Element(Style).AlignRight().Text("Count");
header.Cell().Element(Style).AlignRight().Text("Price");
header.Cell().Element(Style).AlignRight().Text("Total");
IContainer Style(IContainer container)
{
return container
.DefaultTextStyle(x => x.SemiBold())
.BorderBottom(1)
.BorderColor(Colors.Grey.Darken2)
.Padding(5);
}
});
table.Footer(footer =>
{
footer
.Cell().ColumnSpan(5)
.AlignRight()
.PaddingTop(10)
.Text($"Subtotal: {total}$", TextStyle.Default.Bold());
});
foreach (var index in Enumerable.Range(State.ShownItemsCount, itemsToDisplay))
{
var item = Items[index];
table.Cell().Element(Style).Text(index + 1);
table.Cell().Element(Style).Text(item.ItemName);
table.Cell().Element(Style).AlignRight().Text(item.Count);
table.Cell().Element(Style).AlignRight().Text($"{item.Price}$");
table.Cell().Element(Style).AlignRight().Text($"{item.Count*item.Price}$");
IContainer Style(IContainer container)
{
return container
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.Padding(5);
}
}
});
});
}
}
public static class DynamicSimpleTableExample
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ShowResults()
.ProduceImages()
.Render(container =>
{
var items = Enumerable.Range(0, 15).Select(x => new OrderItem()).ToList();
container
.Background(Colors.White)
.Padding(25)
.DefaultTextStyle(x => x.FontSize(16))
.Decoration(decoration =>
{
decoration
.Header()
.PaddingBottom(5)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().FontColor(Colors.Blue.Darken2));
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
decoration
.Content()
.Dynamic(new OrdersTable(items));
});
});
}
}
}
+24 -12
View File
@@ -43,7 +43,9 @@ namespace QuestPDF.Examples
.Before()
.Background(Colors.Grey.Medium)
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
.Text("Notes")
.FontSize(16)
.FontColor("#FFF");
decoration
.Content()
@@ -280,12 +282,15 @@ namespace QuestPDF.Examples
.Layer()
.AlignCenter()
.AlignMiddle()
.Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
.Text("Watermark")
.FontSize(48)
.Bold()
.FontColor(Colors.Green.Lighten3);
layers
.Layer()
.AlignBottom()
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
.Text(text => text.CurrentPageNumber().FontSize(16).FontColor(Colors.Green.Medium));
});
});
}
@@ -416,7 +421,9 @@ namespace QuestPDF.Examples
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Padding(10)
.Text(font, TextStyle.Default.FontType(font).Size(16));
.Text(font)
.FontFamily(font)
.FontSize(16);
}
});
});
@@ -456,7 +463,7 @@ namespace QuestPDF.Examples
});
layers.Layer().Background("#8F00").Extend();
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
layers.Layer().PaddingTop(40).Text("It works!").FontSize(24);
});
});
}
@@ -477,7 +484,7 @@ namespace QuestPDF.Examples
//.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("Test of the \n box element", TextStyle.Default.Size(20));
.Text("Test of the \n box element").FontSize(20);
});
}
@@ -503,7 +510,8 @@ namespace QuestPDF.Examples
decoration
.Before()
.PaddingBottom(10)
.Text("Example: scale component", headerFontStyle);
.Text("Example: scale component")
.Style(headerFontStyle);
decoration
.Content()
@@ -525,7 +533,8 @@ namespace QuestPDF.Examples
.Background(fontColor)
.Scale(scale)
.Padding(5)
.Text($"Content with {scale} scale.", fontStyle);
.Text($"Content with {scale} scale.")
.Style(fontStyle);
}
});
});
@@ -555,7 +564,8 @@ namespace QuestPDF.Examples
.BorderColor(Colors.Green.Darken1)
.Padding(50)
.Text("Moved text", TextStyle.Default.Size(25));
.Text("Moved text")
.FontSize(25);
});
}
@@ -591,7 +601,8 @@ namespace QuestPDF.Examples
.MinimalBox()
.Background(Colors.White)
.Padding(10)
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
.Text($"Rotated {turns * 90}°")
.FontSize(16);
}
});
});
@@ -689,7 +700,8 @@ namespace QuestPDF.Examples
.MinimalBox()
.Background(Colors.White)
.Padding(10)
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
.Text($"Flipped {turns}")
.FontSize(16);
}
});
});
@@ -730,7 +742,7 @@ namespace QuestPDF.Examples
{
container
.Padding(25)
.PaddingLeft(75)
.PaddingLeft(50)
.Column(column =>
{
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
+4 -2
View File
@@ -23,6 +23,8 @@ namespace QuestPDF.Examples.Engine
private bool ApplyCaching { get; set; }
private bool ApplyDebugging { get; set; }
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
private bool ShowingResultsEnabled = true;
private RenderingTest()
{
@@ -114,7 +116,7 @@ namespace QuestPDF.Examples.Engine
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
document.GenerateImages(fileNameSchema);
if (ShowResult)
if (ShowResult && ShowingResultsEnabled)
Process.Start("explorer", fileNameSchema(0));
}
@@ -123,7 +125,7 @@ namespace QuestPDF.Examples.Engine
var fileName = $"{FileNamePrefix}.pdf";
document.GeneratePdf(fileName);
if (ShowResult)
if (ShowResult && ShowingResultsEnabled)
Process.Start("explorer", fileName);
}
}
+2 -2
View File
@@ -21,9 +21,9 @@ namespace QuestPDF.Examples
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.PageColor(Colors.White);
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
page.Header().Text("With ensure space").SemiBold();
page.Content().Column(column =>
{
@@ -0,0 +1,76 @@
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ExecutionOrderExamples
{
[Test]
public void Example()
{
RenderingTest
.Create()
.PageSize(400, 300)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.DefaultTextStyle(TextStyle.Default.Size(18))
.Padding(25)
.Row(row =>
{
row.Spacing(25);
row.RelativeItem()
.Border(1)
.Padding(15)
.Background(Colors.Grey.Lighten2)
.Text("Lorem ipsum");
row.RelativeItem()
.Border(1)
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("dolor sit amet");
});
});
}
[Test]
public void Example2()
{
RenderingTest
.Create()
.PageSize(200, 200)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Border(2)
.Width(150)
.Height(150)
.Background(Colors.Blue.Lighten2)
.PaddingTop(50)
.Background(Colors.Green.Lighten2)
.PaddingRight(50)
.Background(Colors.Red.Lighten2)
.PaddingBottom(50)
.Background(Colors.Amber.Lighten2)
.PaddingLeft(50)
.Background(Colors.Grey.Lighten2);
});
}
}
}
+2 -2
View File
@@ -17,9 +17,9 @@ namespace QuestPDF.Examples
.Padding(5);
}
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold();
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text);
}
public class FrameExample
+42
View File
@@ -0,0 +1,42 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class GridBenchmark
{
[Test]
public void Benchmark()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.MaxPages(10_000)
.EnableCaching(true)
.EnableDebugging(false)
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Grid(grid =>
{
const int numberOfRows = 100_000;
const int numberOfColumns = 10;
grid.Columns(numberOfColumns);
foreach (var row in Enumerable.Range(0, numberOfRows))
foreach (var column in Enumerable.Range(0, numberOfColumns))
grid.Item().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
});
});
}
}
}
+2 -1
View File
@@ -74,7 +74,8 @@ namespace QuestPDF.Examples
.PrimaryLayer()
.AlignCenter()
.AlignMiddle()
.Text(sizeText, TextStyle.Default.Size(15));
.Text(sizeText)
.FontSize(15);
});
}
});
+22 -15
View File
@@ -3,6 +3,7 @@ using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
@@ -14,16 +15,20 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProducePdf()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container.Padding(25).Column(column =>
{
column.Item().Text("Above text");
column.Item().PaddingVertical(10).LineHorizontal(1).LineColor(Colors.Blue.Accent1);
column.Item().Text("Below text");
});
container
.Padding(15)
.MinimalBox()
.DefaultTextStyle(TextStyle.Default.Size(16))
.Column(column =>
{
column.Item().Text("Above text");
column.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Medium);
column.Item().Text("Below text");
});
});
}
@@ -33,17 +38,19 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProducePdf()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container.Padding(25).Inlined(inlined =>
{
inlined.Spacing(5);
inlined.Item().Text("Above text");
inlined.Item().LineVertical(1).LineColor(Colors.Blue.Accent1);
inlined.Item().Text("Below text");
});
container
.Padding(15)
.DefaultTextStyle(TextStyle.Default.Size(16))
.Row(row =>
{
row.AutoItem().Text("Left text");
row.AutoItem().PaddingHorizontal(10).LineVertical(1).LineColor(Colors.Grey.Medium);
row.AutoItem().Text("Right text");
});
});
}
}
+53
View File
@@ -0,0 +1,53 @@
using System.Diagnostics;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class MinimalApiExamples
{
[Test]
public void MinimalApi()
{
Document
.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header()
.Text("Hello PDF!")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.GeneratePdf("hello.pdf");
Process.Start("explorer.exe", "hello.pdf");
}
}
}
+2 -1
View File
@@ -50,7 +50,8 @@ namespace QuestPDF.Examples
.Background("FFF")
.Padding(5)
.AlignCenter()
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
.Text("Sample text")
.FontFamily("Segoe UI emoji");
});
}
@@ -0,0 +1,57 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class PageBackgroundForegroundExample
{
[Test]
public void PageBackgroundForeground()
{
RenderingTest
.Create()
.ProduceImages()
.MaxPages(100)
.ShowResults()
.RenderDocument(document =>
{
document.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(1, Unit.Inch);
page.DefaultTextStyle(TextStyle.Default.FontSize(16));
page.PageColor(Colors.White);
const string transparentBlue = "#662196f3";
page.Background()
.AlignTop()
.ExtendHorizontal()
.Height(200)
.Background(transparentBlue);
page.Foreground()
.AlignBottom()
.ExtendHorizontal()
.Height(250)
.Background(transparentBlue);
page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Darken2).FontSize(36);
page.Content().PaddingVertical(25).Column(column =>
{
column.Spacing(25);
foreach (var i in Enumerable.Range(0, 100))
column.Item().Background(Colors.Grey.Lighten2).Height(75);
});
});
});
}
}
}
+6 -2
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
@@ -11,7 +11,8 @@
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="SkiaSharp" Version="2.80.4" />
<PackageReference Include="Svg.Skia" Version="0.5.10" />
</ItemGroup>
<ItemGroup>
@@ -28,6 +29,9 @@
<None Update="logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="pdf-icon.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+16
View File
@@ -89,5 +89,21 @@ namespace QuestPDF.Examples
.ShowOnce();
}
}
[Test]
public void Stability_NoItems()
{
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.Render(container =>
{
container
.Padding(25)
.Row(row => { });
});
}
}
}
+4 -4
View File
@@ -15,7 +15,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.ProduceImages()
.ShowResults()
.Render(container =>
{
@@ -23,15 +23,15 @@ namespace QuestPDF.Examples
{
var text = Placeholders.Paragraph();
foreach (var i in Enumerable.Range(0, 16))
foreach (var i in Enumerable.Range(2, 5))
{
column
.Item()
.MinimalBox()
.Border(1)
.Padding(5)
.Width(50 + i * 25)
.Height(25 + i * 5)
.Width(i * 40)
.Height(i * 20)
.ScaleToFit()
.Text(text);
}
+2 -2
View File
@@ -21,9 +21,9 @@ namespace QuestPDF.Examples
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.PageColor(Colors.White);
page.Header().Text("With show once", TextStyle.Default.SemiBold());
page.Header().Text("With show once").SemiBold();
page.Content().PaddingVertical(5).Row(row =>
{
+3 -2
View File
@@ -21,7 +21,7 @@ namespace QuestPDF.Examples
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.PageColor(Colors.White);
page.Header().Column(column =>
{
@@ -31,7 +31,8 @@ namespace QuestPDF.Examples
page.Content()
.PaddingVertical(10)
.Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
.Text(Placeholders.Paragraphs())
.FontColor(Colors.Grey.Medium);
page.Footer().Text(text =>
{
+48
View File
@@ -0,0 +1,48 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class StopPaging
{
[Test]
public void Example()
{
RenderingTest
.Create()
.PageSize(300, 250)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.DefaultTextStyle(TextStyle.Default.Size(14))
.Decoration(decoration =>
{
decoration
.Before()
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Medium));
text.Span("Page ");
text.CurrentPageNumber();
});
decoration
.Content()
.Column(column =>
{
column.Spacing(25);
column.Item().StopPaging().Text(Placeholders.LoremIpsum());
column.Item().ExtendHorizontal().Height(75).Background(Colors.Grey.Lighten2);
});
});
});
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
using Svg.Skia;
namespace QuestPDF.Examples
{
public class SvgImageExample
{
[Test]
public void BorderRadius()
{
RenderingTest
.Create()
.PageSize(175, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.Grey.Lighten2)
.Padding(25)
.Canvas((canvas, space) =>
{
using var svg = new SKSvg();
svg.Load("pdf-icon.svg");
canvas.DrawPicture(svg.Picture);
});
});
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class TableBenchmark
{
[Test]
public void Benchmark()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.MaxPages(10_000)
.EnableCaching(true)
.EnableDebugging(false)
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
const int numberOfRows = 100_000;
const int numberOfColumns = 10;
table.ColumnsDefinition(columns =>
{
foreach (var _ in Enumerable.Range(0, numberOfColumns))
columns.RelativeColumn();
});
foreach (var row in Enumerable.Range(0, numberOfRows))
foreach (var column in Enumerable.Range(0, numberOfColumns))
table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
});
});
}
}
}
+37 -2
View File
@@ -49,6 +49,39 @@ namespace QuestPDF.Examples
});
}
[Test]
public void PagingSupport()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(420, 220)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
// by using custom 'Element' method, we can reuse visual configuration
table.Cell().Element(Block).Text(Placeholders.Label());
table.Cell().Element(Block).Text(Placeholders.Label());
table.Cell().Element(Block).Text(Placeholders.Paragraph());
table.Cell().Element(Block).Text(Placeholders.Label());
});
});
}
[Test]
public void DefaultCellStyle()
{
@@ -217,7 +250,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 220)
.PageSize(220, 170)
.ShowResults()
.Render(container =>
{
@@ -380,7 +413,7 @@ namespace QuestPDF.Examples
.EnableCaching()
.EnableDebugging(false)
.ShowResults()
.Render(container => GeneratePerformanceStructure(container, 1000));
.Render(container => GeneratePerformanceStructure(container, 250));
}
public static void GeneratePerformanceStructure(IContainer container, int repeats)
@@ -456,8 +489,10 @@ namespace QuestPDF.Examples
return container
.Border(1)
.Background(Colors.Grey.Lighten3)
.ShowOnce()
.MinWidth(50)
.MinHeight(50)
.Padding(10)
.AlignCenter()
.AlignMiddle();
}
+27 -34
View File
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(x => ComposeBook(x, chapters));
.RenderDocument(x => ComposeBook(x, chapters));
}
[Test]
@@ -31,7 +31,7 @@ namespace QuestPDF.Examples
{
var chapters = GetBookChapters().ToList();
var results = PerformTest(16).ToList();
var results = PerformTest(128).ToList();
Console.WriteLine($"Min: {results.Min():F}");
Console.WriteLine($"Max: {results.Max():F}");
@@ -42,8 +42,8 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.Render(x => ComposeBook(x, chapters));
.ProducePdf()
.RenderDocument(x => ComposeBook(x, chapters));
}
IEnumerable<float> PerformTest(int attempts)
@@ -102,37 +102,30 @@ namespace QuestPDF.Examples
}
}
private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
private void ComposeBook(IDocumentContainer container, ICollection<BookChapter> chapters)
{
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
var normalStyle = TextStyle.Default.Size(14);
ComposePage(container);
void ComposePage(IContainer container)
container.Page(page =>
{
container
.Padding(50)
.Decoration(decoration =>
{
decoration
.Content()
.Column(column =>
{
column.Item().Element(Title);
column.Item().PageBreak();
column.Item().Element(TableOfContents);
column.Item().PageBreak();
page.Margin(50);
page.Content().Column(column =>
{
column.Item().Element(Title);
column.Item().PageBreak();
column.Item().Element(TableOfContents);
column.Item().PageBreak();
Chapters(column);
Chapters(column);
column.Item().Element(Acknowledgements);
});
column.Item().Element(Acknowledgements);
});
page.Footer().Element(Footer);
});
decoration.After().Element(Footer);
});
}
void Title(IContainer container)
{
container
@@ -141,8 +134,8 @@ namespace QuestPDF.Examples
.AlignBottom()
.Column(column =>
{
column.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
column.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
column.Item().Text("Quo Vadis").FontSize(72).Bold().FontColor(Colors.Blue.Darken2);
column.Item().Text("Henryk Sienkiewicz").FontSize(24).FontColor(Colors.Grey.Darken2);
});
}
@@ -156,8 +149,8 @@ namespace QuestPDF.Examples
{
column.Item().InternalLink(chapter.Title).Row(row =>
{
row.RelativeItem().Text(chapter.Title, normalStyle);
row.ConstantItem(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
row.RelativeItem().Text(chapter.Title).Style(normalStyle);
row.ConstantItem(100).AlignRight().Text(text => text.BeginPageNumberOfSection(chapter.Title).Style(normalStyle));
});
}
});
@@ -180,7 +173,7 @@ namespace QuestPDF.Examples
column.Item().Text(text =>
{
text.ParagraphSpacing(5);
text.Span(content, normalStyle);
text.Span(content).Style(normalStyle);
});
column.Item().PageBreak();
@@ -198,7 +191,7 @@ namespace QuestPDF.Examples
text.DefaultTextStyle(normalStyle);
text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").FontColor(Colors.Blue.Medium).Underline();
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
});
});
@@ -206,7 +199,7 @@ namespace QuestPDF.Examples
void SectionTitle(ColumnDescriptor column, string text)
{
column.Item().Location(text).Text(text, subtitleStyle);
column.Item().Location(text).Text(text).Style(subtitleStyle);
column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
}
+376 -26
View File
@@ -12,11 +12,11 @@ namespace QuestPDF.Examples
public class TextExamples
{
[Test]
public void SimpleTextBlock()
public void SimpleText()
{
RenderingTest
.Create()
.PageSize(500, 300)
.PageSize(500, 100)
.ProduceImages()
.ShowResults()
@@ -27,21 +27,192 @@ namespace QuestPDF.Examples
.MinimalBox()
.Border(1)
.Padding(10)
.Text(Placeholders.Paragraph());
});
}
[Test]
public void SimpleTextBlock()
{
RenderingTest
.Create()
.PageSize(600, 300)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.MinimalBox()
.Border(1)
.MaxWidth(300)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
text.Span("This is a normal text, followed by an ");
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
text.Span("underlined red text").FontColor(Colors.Red.Medium).Underline();
text.Span(".");
});
});
}
[Test]
public void TextWeight()
{
RenderingTest
.Create()
.PageSize(500, 500)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.MinimalBox()
.Border(1)
.Padding(20)
.Text(text =>
{
text.DefaultTextStyle(x => x.FontFamily(Fonts.Calibri).FontSize(20));
text.Line("Thin").Thin();
text.Line("ExtraLight").ExtraLight();
text.Line("Light").Light();
text.Line("NormalWeight").NormalWeight();
text.Line("Medium").Medium();
text.Line("SemiBold").SemiBold();
text.Line("Bold").Bold();
text.Line("ExtraBold").ExtraBold();
text.Line("Black").Black();
text.Line("ExtraBlack").ExtraBlack();
});
});
}
[Test]
public void LineHeight()
{
RenderingTest
.Create()
.PageSize(500, 700)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Column(column =>
{
var lineHeights = new[] { 0.8f, 1f, 1.5f };
var paragraph = Placeholders.Paragraph();
foreach (var lineHeight in lineHeights)
{
column
.Item()
.Border(1)
.Padding(10)
.Text(paragraph)
.FontSize(16)
.LineHeight(lineHeight);
}
});
});
}
[Test]
public void SuperscriptSubscript_Simple()
{
RenderingTest
.Create()
.PageSize(500, 500)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.MinimalBox()
.Border(1)
.Padding(20)
.Text(text =>
{
text.DefaultTextStyle(x => x.FontSize(20));
text.ParagraphSpacing(10);
var highlight = TextStyle.Default.BackgroundColor(Colors.Green.Lighten3);
text.Span("E=mc").Style(highlight);
text.Span("2").Superscript().Style(highlight);
text.Span(" is the equation of massenergy equivalence.");
text.EmptyLine();
text.Span("H").Style(highlight);
text.Span("2").Subscript().Style(highlight);
text.Span("O").Style(highlight);
text.Span(" is the chemical formula for water.");
});
});
}
[Test]
public void SuperscriptSubscript_Effects()
{
RenderingTest
.Create()
.PageSize(800, 400)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.DefaultTextStyle(x => x.FontSize(30))
.Column(column =>
{
column.Spacing(25);
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.Underline());
text.Span("Underline of the superscript (E = mc");
text.Span("2").Superscript();
text.Span(") should be at the same height as for normal text.");
});
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.Underline());
text.Span("Underline of the subscript(H");
text.Span("2").Subscript();
text.Span("O) should be slightly lower than a normal text.");
});
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.Strikethrough());
text.Span("Strikethrough of both superscript (E=mc");
text.Span("2").Superscript();
text.Span(") and subscript(H");
text.Span("2").Subscript();
text.Span("O) should be visible in the middle of the text.");
});
});
});
}
[Test]
public void ParagraphSpacing()
{
RenderingTest
.Create()
.PageSize(500, 300)
.PageSize(500, 500)
.ProduceImages()
.ShowResults()
.Render(container =>
@@ -53,13 +224,7 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.ParagraphSpacing(10);
foreach (var i in Enumerable.Range(1, 3))
{
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
text.Line(Placeholders.Paragraph());
}
text.Line(Placeholders.Paragraph());
});
});
}
@@ -81,7 +246,7 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
text.Span("This is a random image aligned to the baseline: ");
text.Element()
@@ -120,7 +285,7 @@ namespace QuestPDF.Examples
text.Line(Placeholders.LoremIpsum());
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <", TextStyle.Default.Underline());
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <").Underline();
});
});
}
@@ -173,6 +338,8 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(x => x.Bold());
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
@@ -182,14 +349,14 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span("This text is a normal text, ");
text.Span("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("this is a bold text, ").Bold();
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
text.Span("and this is slightly bigger text.").FontSize(16);
text.EmptyLine();
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
@@ -201,7 +368,7 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.Hyperlink("Please visit QuestPDF website", "https://www.questpdf.com");
text.EmptyLine();
@@ -210,7 +377,7 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
text.Span(Placeholders.Paragraphs()).Italic();
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
});
@@ -236,17 +403,17 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.DefaultTextStyle(TextStyle.Default.FontSize(20).BackgroundColor(Colors.Red.Lighten4));
text.AlignLeft();
text.ParagraphSpacing(10);
text.Span("This text is a normal text, ");
text.Span("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("this is a bold text, ").Bold();
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
text.Span("and this is slightly bigger text.").FontSize(16);
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
@@ -255,7 +422,7 @@ namespace QuestPDF.Examples
{
text.Line($"{i}: {Placeholders.Paragraph()}");
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.Hyperlink("Please visit QuestPDF website. ", "https://www.questpdf.com");
text.Span("This is page number ");
text.CurrentPageNumber();
@@ -267,5 +434,188 @@ namespace QuestPDF.Examples
});
});
}
[Test]
public void MeasureIssueWhenSpaceAtLineEnd()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text("This is a specially crafted sentence with a specially chosen length for demonstration of the bug that occurs ;;;;;. ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
});
});
}
[Test]
public void EmptyText()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text(" ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
});
});
}
[Test]
public void Whitespaces()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text(" x ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
});
});
}
[Test]
public void DrawingNullTextShouldNotThrowException()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Column(column =>
{
column.Item().Text(null);
column.Item().Text(text =>
{
text.Span(null);
text.Line(null);
text.Hyperlink(null, "http://www.questpdf.com");
text.TotalPages().Format(x => null);
});
});
});
});
}
[Test]
public void BreakingLongWord()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Column(column =>
{
column.Item().Text(null);
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.BackgroundColor(Colors.Red.Lighten3).FontSize(24));
text.Span(" " + Placeholders.LoremIpsum());
text.Span(" 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 ").WrapAnywhere();
});
});
});
});
}
[Test]
public void TextShaping_Unicode()
{
RenderingTest
.Create()
.PageSize(600, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(35)
.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
text.Span("Complex Unicode structure: ");
text.Span("T̶̖̔͆͆̽̔ḩ̷̼̫̐̈́̀͜͝͝ì̶͇̤͓̱̣͇͓͉̎s̵̡̟̹͍̜͉̗̾͛̈̐́͋͂͝͠ͅ ̴̨͙͍͇̭̒͗̀́͝ì̷̡̺͉̼̏̏̉̌͝s̷͍͙̗̰̖͙̈̑̂̔͑͊̌̓̊̇͜ ̶̛̼͚͊̅͘ṭ̷̨̘̣̙̖͉͌̏̂̅͑̄̽̕͝ȅ̶̲̲̙̭͈̬̣͔̝͔̈́͝s̸̢̯̪̫͓̭̮̓̀͆͜ț̸̢͉̞̥̤̏̌̓͝").FontColor(Colors.Red.Medium);
text.Span(".");
});
});
}
[Test]
public void TextShaping_Arabic()
{
RenderingTest
.Create()
.PageSize(500, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Text("ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا")
.FontSize(20);
});
}
}
}
+149
View File
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="75.320129mm"
height="92.604164mm"
viewBox="0 0 75.320129 92.604164"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="PDF file icon.svg">
<title
id="title2682">PDF file icon</title>
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath839">
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="rect841"
width="68.688263"
height="67.886459"
x="-74.033661"
y="141.44913" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="122.12727"
inkscape:cy="167.02823"
inkscape:document-units="mm"
inkscape:current-layer="g899"
showgrid="false"
inkscape:object-nodes="false"
inkscape:window-width="1366"
inkscape:window-height="705"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-page="false"
inkscape:snap-others="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>PDF file icon</dc:title>
<dc:date>08/10/2018</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Adobe Systems</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>CMetalCore</dc:title>
</cc:Agent>
</dc:publisher>
<dc:description>Fuente del texto &quot;PDF&quot;:
Franklin Gothic Medium Cond</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(36.076172,-93.731774)">
<g
id="g899"
transform="matrix(1.4843054,0,0,1.4843054,17.471885,-90.243502)">
<g
id="g876">
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff2116;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -29.632812,123.94727 c -3.551967,0 -6.44336,2.89347 -6.44336,6.44531 v 49.49804 c 0,3.55185 2.891393,6.44532 6.44336,6.44532 H 8.2167969 c 3.5519661,0 6.4433591,-2.89335 6.4433591,-6.44532 v -40.70117 c 0,0 0.101353,-1.19181 -0.416015,-2.35156 -0.484969,-1.08711 -1.275391,-1.84375 -1.275391,-1.84375 a 1.0584391,1.0584391 0 0 0 -0.0059,-0.008 L 3.5722246,125.7752 a 1.0584391,1.0584391 0 0 0 -0.015625,-0.0156 c 0,0 -0.8017392,-0.76344 -1.9902344,-1.27344 -1.39939552,-0.6005 -2.8417968,-0.53711 -2.8417968,-0.53711 l 0.021484,-0.002 z"
id="path890" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -29.632812,126.06445 h 28.3789058 a 1.0584391,1.0584391 0 0 0 0.021484,0 c 0,0 1.13480448,0.011 1.96484378,0.36719 0.79889772,0.34282 1.36536982,0.86176 1.36914062,0.86524 1.25e-5,1e-5 0.00391,0.004 0.00391,0.004 l 9.3671868,9.18945 c 0,0 0.564354,0.59582 0.837891,1.20899 0.220779,0.49491 0.234375,1.40039 0.234375,1.40039 a 1.0584391,1.0584391 0 0 0 -0.002,0.0449 v 40.74609 c 0,2.41592 -1.910258,4.32813 -4.3261717,4.32813 H -29.632812 c -2.415914,0 -4.326172,-1.91209 -4.326172,-4.32813 v -49.49804 c 0,-2.41603 1.910258,-4.32813 4.326172,-4.32813 z"
id="rect2684" />
<path
sodipodi:nodetypes="scccscccscccccssscacccsccccccccccccccccccccaccccccccc"
inkscape:connector-curvature="0"
d="m -23.40766,161.09299 c -1.45669,-1.45669 0.11934,-3.45839 4.39648,-5.58397 l 2.69124,-1.33743 1.04845,-2.29399 c 0.57665,-1.26169 1.43729,-3.32036 1.91254,-4.5748 l 0.8641,-2.28082 -0.59546,-1.68793 c -0.73217,-2.07547 -0.99326,-5.19438 -0.52872,-6.31588 0.62923,-1.51909 2.69029,-1.36323 3.50626,0.26515 0.63727,1.27176 0.57212,3.57488 -0.18329,6.47946 l -0.6193,2.38125 0.5455,0.92604 c 0.30003,0.50932 1.1764,1.71867 1.9475,2.68743 l 1.44924,1.80272 1.8033728,-0.23533 c 5.72900399,-0.74758 7.6912472,0.523 7.6912472,2.34476 0,2.29921 -4.4984914,2.48899 -8.2760865,-0.16423 -0.8499666,-0.59698 -1.4336605,-1.19001 -1.4336605,-1.19001 0,0 -2.3665326,0.48178 -3.531704,0.79583 -1.202707,0.32417 -1.80274,0.52719 -3.564509,1.12186 0,0 -0.61814,0.89767 -1.02094,1.55026 -1.49858,2.4279 -3.24833,4.43998 -4.49793,5.1723 -1.3991,0.81993 -2.86584,0.87582 -3.60433,0.13733 z m 2.28605,-0.81668 c 0.81883,-0.50607 2.47616,-2.46625 3.62341,-4.28553 l 0.46449,-0.73658 -2.11497,1.06339 c -3.26655,1.64239 -4.76093,3.19033 -3.98386,4.12664 0.43653,0.52598 0.95874,0.48237 2.01093,-0.16792 z m 21.21809,-5.95578 c 0.80089,-0.56097 0.68463,-1.69142 -0.22082,-2.1472 -0.70466,-0.35471 -1.2726074,-0.42759 -3.1031574,-0.40057 -1.1249,0.0767 -2.9337647,0.3034 -3.2403347,0.37237 0,0 0.993716,0.68678 1.434896,0.93922 0.58731,0.33544 2.0145161,0.95811 3.0565161,1.27706 1.02785,0.31461 1.6224,0.28144 2.0729,-0.0409 z m -8.53152,-3.54594 c -0.4847,-0.50952 -1.30889,-1.57296 -1.83152,-2.3632 -0.68353,-0.89643 -1.02629,-1.52887 -1.02629,-1.52887 0,0 -0.4996,1.60694 -0.90948,2.57394 l -1.27876,3.16076 -0.37075,0.71695 c 0,0 1.971043,-0.64627 2.97389,-0.90822 1.0621668,-0.27744 3.21787,-0.70134 3.21787,-0.70134 z m -2.74938,-11.02573 c 0.12363,-1.0375 0.1761,-2.07346 -0.15724,-2.59587 -0.9246,-1.01077 -2.04057,-0.16787 -1.85154,2.23517 0.0636,0.8084 0.26443,2.19033 0.53292,3.04209 l 0.48817,1.54863 0.34358,-1.16638 c 0.18897,-0.64151 0.47882,-2.02015 0.64411,-3.06364 z"
style="fill:#ff2116;fill-opacity:1;stroke-width:0.26458335"
id="path2697" />
<g
id="g858">
<path
inkscape:connector-curvature="0"
id="path845"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -20.930423,167.83862 h 2.364986 q 1.133514,0 1.840213,0.2169 0.706698,0.20991 1.189489,0.9446 0.482795,0.72769 0.482795,1.75625 0,0.94459 -0.391832,1.6233 -0.391833,0.67871 -1.056548,0.97958 -0.65772,0.30087 -2.02913,0.30087 h -0.818651 v 3.72941 h -1.581322 z m 1.581322,1.22447 v 3.33058 h 0.783664 q 1.049552,0 1.44838,-0.39184 0.405826,-0.39183 0.405826,-1.27345 0,-0.65772 -0.265887,-1.06355 -0.265884,-0.41282 -0.587747,-0.50378 -0.314866,-0.098 -1.000572,-0.098 z" />
<path
inkscape:connector-curvature="0"
id="path847"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -13.842461,167.83862 h 2.148082 q 1.560333,0 2.4909318,0.55276 0.9375993,0.55276 1.4133973,1.6443 0.482791,1.09153 0.482791,2.42096 0,1.3994 -0.4338151,2.49793 -0.4268149,1.09153 -1.3154348,1.76324 -0.8816233,0.67172 -2.5189212,0.67172 h -2.267031 z m 1.581326,1.26645 v 7.018 h 0.657715 q 1.378411,0 2.001144,-0.9516 0.6227329,-0.95858 0.6227329,-2.5539 0,-3.5125 -2.6238769,-3.5125 z" />
<path
inkscape:connector-curvature="0"
id="path849"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -5.7889096,167.83862 h 5.30372941 v 1.26645 H -4.2075842 v 2.85478 h 2.9807225 v 1.26646 h -2.9807225 v 4.16322 h -1.5813254 z" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

+62
View File
@@ -0,0 +1,62 @@
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer;
using QuestPDF.ReportSample;
using QuestPDF.ReportSample.Layouts;
//ImagePlaceholder.Solid = true;
var model = DataSource.GetReport();
var report = new StandardReport(model);
report.ShowInPreviewer();
return;
Document
.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header()
.Text("Hot Reload!")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Darken2);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Table(t =>
{
t.ColumnsDefinition(c =>
{
c.RelativeColumn();
c.RelativeColumn(3);
});
t.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Visual Studio");
t.Cell().Border(1).Padding(5).Text("Start in debug mode with 'Hot Reload on Save' enabled.");
t.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Command line");
t.Cell().Border(1).Padding(5).Text("Run 'dotnet watch'.");
});
x.Item().Text("Modify this line and the preview should show your changes instantly.");
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.ShowInPreviewer();
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\QuestPDF.ReportSample\QuestPDF.ReportSample.csproj" />
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,79 @@
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SkiaSharp;
namespace QuestPDF.Previewer;
class CommunicationService
{
public static CommunicationService Instance { get; } = new ();
public event Action<ICollection<PreviewPage>>? OnDocumentRefreshed;
private WebApplication? Application { get; set; }
private readonly JsonSerializerOptions JsonSerializerOptions = new()
{
PropertyNameCaseInsensitive = true
};
private CommunicationService()
{
}
public Task Start(int port)
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddLogging(x => x.ClearProviders());
builder.WebHost.UseKestrel(options => options.Limits.MaxRequestBodySize = null);
Application = builder.Build();
Application.MapGet("ping", HandlePing);
Application.MapGet("version", HandleVersion);
Application.MapPost("update/preview", HandleUpdatePreview);
return Application.RunAsync($"http://localhost:{port}/");
}
public async Task Stop()
{
await Application.StopAsync();
await Application.DisposeAsync();
}
private async Task<IResult> HandlePing()
{
return OnDocumentRefreshed == null
? Results.StatusCode(StatusCodes.Status503ServiceUnavailable)
: Results.Ok();
}
private async Task<IResult> HandleVersion()
{
return Results.Json(GetType().Assembly.GetName().Version);
}
private async Task<IResult> HandleUpdatePreview(HttpRequest request)
{
var command = JsonSerializer.Deserialize<DocumentSnapshot>(request.Form["command"], JsonSerializerOptions);
var pages = command
.Pages
.Select(page =>
{
using var stream = request.Form.Files[page.Id].OpenReadStream();
var picture = SKPicture.Deserialize(stream);
return new PreviewPage(picture, page.Width, page.Height);
})
.ToList();
Task.Run(() => OnDocumentRefreshed(pages));
return Results.Ok();
}
}
+20
View File
@@ -0,0 +1,20 @@
using SkiaSharp;
namespace QuestPDF.Previewer;
class Helpers
{
public static void GeneratePdfFromDocumentSnapshots(string filePath, ICollection<PreviewPage> pages)
{
using var stream = File.Create(filePath);
using var document = SKDocument.CreatePdf(stream);
foreach (var page in pages)
{
using var canvas = document.BeginPage(page.Width, page.Height);
canvas.DrawPicture(page.Picture);
document.EndPage();
}
}
}
+344
View File
@@ -0,0 +1,344 @@
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Skia;
using DynamicData;
using SkiaSharp;
namespace QuestPDF.Previewer;
class InteractiveCanvas : ICustomDrawOperation
{
public Rect Bounds { get; set; }
public ICollection<PreviewPage> Pages { get; set; }
public InspectionElement? InspectionElement { get; set; }
private float ViewportWidth => (float)Bounds.Width;
private float ViewportHeight => (float)Bounds.Height;
public float Scale { get; private set; } = 1;
public float TranslateX { get; set; }
public float TranslateY { get; set; }
private const float MinScale = 0.1f;
private const float MaxScale = 10f;
private const float PageSpacing = 25f;
private const float SafeZone = 25f;
public float TotalPagesHeight => Pages.Sum(x => x.Height) + (Pages.Count - 1) * PageSpacing;
public float TotalHeight => TotalPagesHeight + SafeZone * 2 / Scale;
public float MaxWidth => Pages.Any() ? Pages.Max(x => x.Width) : 0;
public float MaxTranslateY => TotalHeight - ViewportHeight / Scale;
public float ScrollPercentY
{
get
{
return TranslateY / MaxTranslateY;
}
set
{
TranslateY = value * MaxTranslateY;
}
}
public float ScrollViewportSizeY
{
get
{
var viewPortSize = ViewportHeight / Scale / TotalHeight;
return Math.Clamp(viewPortSize, 0, 1);
}
}
#region transformations
private void LimitScale()
{
Scale = Math.Max(Scale, MinScale);
Scale = Math.Min(Scale, MaxScale);
}
private void LimitTranslate()
{
if (TotalPagesHeight > ViewportHeight / Scale)
{
TranslateY = Math.Min(TranslateY, MaxTranslateY);
TranslateY = Math.Max(TranslateY, 0);
}
else
{
TranslateY = (TotalPagesHeight - ViewportHeight / Scale) / 2;
}
if (ViewportWidth / Scale < MaxWidth)
{
var maxTranslateX = (ViewportWidth / 2 - SafeZone) / Scale - MaxWidth / 2;
TranslateX = Math.Min(TranslateX, -maxTranslateX);
TranslateX = Math.Max(TranslateX, maxTranslateX);
}
else
{
TranslateX = 0;
}
}
public void TranslateWithCurrentScale(float x, float y)
{
TranslateX += x / Scale;
TranslateY += y / Scale;
LimitTranslate();
}
public void ZoomToPoint(float x, float y, float factor)
{
var oldScale = Scale;
Scale *= factor;
LimitScale();
TranslateX -= x / Scale - x / oldScale;
TranslateY -= y / Scale - y / oldScale;
LimitTranslate();
}
public int ActivePage { get; set; } = 1;
public IEnumerable<(int pageNumber, float beginY, float endY)> GetPagePosition()
{
var pageNumber = 1;
var currentPagePosition = SafeZone / Scale;
foreach (var page in Pages)
{
yield return (pageNumber, currentPagePosition, currentPagePosition + page.Height);
currentPagePosition += page.Height + PageSpacing;
pageNumber++;
}
}
public void SetActivePage(float x, float y)
{
y /= Scale;
y += TranslateY;
ActivePage = GetPagePosition().FirstOrDefault(p => p.beginY <= y && y <= p.endY).pageNumber;
}
public void ScrollToInspectionElement(InspectionElement element)
{
var location = element.Location.MinBy(x => x.PageNumber);
var pagePosition = GetPagePosition().ElementAt(location.PageNumber - 1);
var page = Pages.ElementAt(location.PageNumber - 1);
var widthScale = ViewportWidth / location.Width;
var heightScale = ViewportHeight / location.Height;
var targetScale = Math.Min(widthScale, heightScale);
targetScale *= 0.7f; // slightly zoom out to show entire element with padding
Scale = targetScale;
TranslateY = pagePosition.beginY + location.Top + location.Height / 2 - ViewportHeight / Scale / 2;
TranslateX = page.Width / 2 - location.Left - location.Width / 2;
}
public (int pageNumber, float x, float y)? FindClickedPointOnThePage(float x, float y)
{
x -= ViewportWidth / 2;
x /= Scale;
x += TranslateX;
y /= Scale;
y += TranslateY;
var location = GetPagePosition().FirstOrDefault(p => p.beginY <= y && y <= p.endY);
if (location == default)
return null;
var page = Pages.ElementAt(location.pageNumber - 1);
x += page.Width / 2;
if (x < 0 || page.Width < x)
return null;
y -= location.beginY;
return (location.pageNumber, x, y);
}
#endregion
#region rendering
public void Render(IDrawingContextImpl context)
{
if (Pages.Count <= 0)
return;
LimitScale();
LimitTranslate();
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
if (canvas == null)
throw new InvalidOperationException($"Context needs to be ISkiaDrawingContextImpl but got {nameof(context)}");
var originalMatrix = canvas.TotalMatrix;
canvas.Translate(ViewportWidth / 2, 0);
canvas.Scale(Scale);
canvas.Translate(TranslateX, -TranslateY);
var topMatrix = canvas.TotalMatrix;;
var positions = GetPagePosition().ToList();
foreach (var pageIndex in Enumerable.Range(0, Pages.Count))
{
canvas.SetMatrix(topMatrix);
var page = Pages.ElementAt(pageIndex);
var position = positions.ElementAt(pageIndex);
canvas.Translate(-page.Width / 2f, position.beginY);
DrawBlankPage(canvas, page.Width, page.Height);
canvas.DrawPicture(page.Picture);
DrawInspectionElement(canvas, pageIndex + 1);
}
canvas.SetMatrix(topMatrix);
DrawActivePage(canvas);
canvas.SetMatrix(originalMatrix);
DrawInnerGradient(canvas);
}
public void Dispose() { }
public bool Equals(ICustomDrawOperation? other) => false;
public bool HitTest(Point p) => true;
#endregion
#region blank page
private static SKPaint BlankPagePaint = new SKPaint
{
Color = SKColors.White
};
private static SKPaint BlankPageShadowPaint = new SKPaint
{
ImageFilter = SKImageFilter.CreateBlendMode(
SKBlendMode.Overlay,
SKImageFilter.CreateDropShadowOnly(0, 6, 6, 6, SKColors.Black.WithAlpha(64)),
SKImageFilter.CreateDropShadowOnly(0, 10, 14, 14, SKColors.Black.WithAlpha(32)))
};
private void DrawBlankPage(SKCanvas canvas, float width, float height)
{
canvas.DrawRect(0, 0, width, height, BlankPageShadowPaint);
canvas.DrawRect(0, 0, width, height, BlankPagePaint);
}
#endregion
#region inner viewport gradient
private const int InnerGradientSize = (int)SafeZone;
private static readonly SKColor InnerGradientColor = SKColor.Parse("#555");
private void DrawInnerGradient(SKCanvas canvas)
{
// gamma correction
var colors = Enumerable
.Range(0, InnerGradientSize)
.Select(x => 1f - x / (float) InnerGradientSize)
.Select(x => Math.Pow(x, 2f))
.Select(x => (byte)(x * 255))
.Select(x => InnerGradientColor.WithAlpha(x))
.ToArray();
using var fogPaint = new SKPaint
{
Shader = SKShader.CreateLinearGradient(
new SKPoint(0, 0),
new SKPoint(0, InnerGradientSize),
colors,
SKShaderTileMode.Clamp)
};
canvas.DrawRect(0, 0, ViewportWidth, InnerGradientSize, fogPaint);
}
#endregion
#region Interactivity
private void DrawActivePage(SKCanvas canvas)
{
if (ActivePage == default)
return;
var page = Pages.ElementAt(ActivePage - 1);
var pagePosition = GetPagePosition().ElementAt(ActivePage - 1);
var thickness = 6f / Scale;
using var strokePaint = new SKPaint
{
StrokeWidth = thickness,
IsStroke = true,
Color = SKColor.Parse("#000")
};
canvas.DrawRect(- page.Width / 2 -thickness / 2, pagePosition.beginY -thickness / 2, page.Width + thickness, page.Height + thickness, strokePaint);
}
private void DrawInspectionElement(SKCanvas canvas, int pageNumber)
{
if (InspectionElement == null || InspectionElement.Location == null)
return;
var location = InspectionElement.Location.FirstOrDefault(x => x.PageNumber == pageNumber);
if (location == null)
return;
var size = 6 / Scale;
size = Math.Min(size, 3);
using var strokePaint1 = new SKPaint
{
StrokeWidth = size,
IsStroke = true,
Color = SKColor.Parse("#42A5F5")
};
using var strokePaint2 = new SKPaint
{
StrokeWidth = size,
IsStroke = true,
PathEffect = SKPathEffect.CreateDash(new[] { size * 3, size * 3 }, 0),
Color = SKColor.Parse("#1E88E5")
};
using var backgroundPaint = new SKPaint
{
Color = SKColor.Parse("#442196F3"),
};
canvas.DrawRect(location.Left, location.Top, location.Width, location.Height, backgroundPaint);
canvas.DrawRect(location.Left + size / 2, location.Top + size / 2, location.Width - size, location.Height - size, strokePaint1);
canvas.DrawRect(location.Left + size / 2, location.Top + size / 2, location.Width - size, location.Height - size, strokePaint2);
}
#endregion
}
+45
View File
@@ -0,0 +1,45 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:previewer="clr-namespace:QuestPDF.Previewer"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="QuestPDF.Previewer.MyHierarchy">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Panel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Name="Highlight" Background="transparent" DoubleTapped="InputElement_OnDoubleTapped" PointerEnter="Highlight_OnPointerEnter" PointerLeave="Indentation_OnPointerLeave" PointerPressed="Clicked"></Panel>
<Panel Grid.Row="0" Grid.Column="0" Name="Indentation"></Panel>
<Panel Grid.Row="0" Grid.Column="1" Name="Panel" Width="16" Height="16" Margin="0,0,8,0" Background="transparent" PointerPressed="Toggle" PointerEnter="Highlight_OnPointerEnter" PointerLeave="Indentation_OnPointerLeave">
<Viewbox Width="24" Height="24">
<Canvas Width="24" Height="24">
<Path Fill="#AFFF" Name="Icon" />
</Canvas>
</Viewbox>
</Panel>
<Panel Grid.Row="0" Grid.Column="2">
<TextBlock Name="ElementName" FontSize="12" Foreground="#AFFF" Margin="0,6" IsHitTestVisible="False"></TextBlock>
</Panel>
<ItemsRepeater Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Name="Repeater" HorizontalCacheLength="1000000" VerticalCacheLength="100000">
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="previewer:InspectionElement">
<previewer:MyHierarchy Hierarchy="{Binding}" OnSelected="MyHierarchy_OnOnSelected"></previewer:MyHierarchy>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Grid>
</UserControl>
+115
View File
@@ -0,0 +1,115 @@
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Color = System.Drawing.Color;
namespace QuestPDF.Previewer
{
public partial class MyHierarchy : UserControl
{
public static readonly StyledProperty<InspectionElement> HierarchyProperty =
AvaloniaProperty.Register<MyHierarchy, InspectionElement>(nameof(Hierarchy));
public InspectionElement Hierarchy
{
get => GetValue(HierarchyProperty);
set => SetValue(HierarchyProperty, value);
}
public static readonly StyledProperty<InspectionElement?> SelectionProperty =
AvaloniaProperty.Register<MyHierarchy, InspectionElement?>(nameof(Selection));
public InspectionElement? Selection
{
get => GetValue(SelectionProperty);
set => SetValue(SelectionProperty, value);
}
public event Action<InspectionElement>? OnSelected;
public bool Extended { get; set; }
public MyHierarchy()
{
InitializeComponent();
HierarchyProperty.Changed.Subscribe(x =>
{
Configure();
});
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
Configure();
}
private void Configure()
{
var panel = this.FindControl<Panel>("Panel");
panel.IsVisible = Hierarchy?.Children?.Any() ?? false;
this.FindControl<Panel>("Indentation").Width = (Hierarchy?.Level ?? 0) * 24;
UpdateIcon();
this.FindControl<TextBlock>("ElementName").Text = Hierarchy?.Text;
this.FindControl<TextBlock>("ElementName").Foreground = new SolidColorBrush(Avalonia.Media.Color.Parse(Hierarchy?.FontColor ?? "#FFF"));
}
private const string ExtendedIcon = "M7,15L12,10L17,15H7Z";
private const string CollapsedIcon = "M7,10L12,15L17,10H7Z";
private void Toggle(object? sender, PointerPressedEventArgs e)
{
Extended = !Extended;
UpdateIcon();
this.FindControl<ItemsRepeater>("Repeater").Items = Extended ? Hierarchy?.Children : Array.Empty<InspectionElement>();
}
private void UpdateIcon()
{
var iconControl = this.FindControl<Avalonia.Controls.Shapes.Path>("Icon");
iconControl.Data = new PathGeometry
{
Figures = PathFigures.Parse(Extended ? ExtendedIcon : CollapsedIcon)
};
}
private void InputElement_OnDoubleTapped(object? sender, RoutedEventArgs e)
{
Toggle(null, null);
}
private void Highlight_OnPointerEnter(object? sender, PointerEventArgs e)
{
Cursor = Cursor.Parse("hand");
this.FindControl<Panel>("Highlight").Background = new SolidColorBrush(Avalonia.Media.Color.Parse("#1FFF"));
}
private void Indentation_OnPointerLeave(object? sender, PointerEventArgs e)
{
Cursor = Cursor.Default;
this.FindControl<Panel>("Highlight").Background = new SolidColorBrush(Avalonia.Media.Color.Parse("#0000"));
}
private void Clicked(object? sender, PointerPressedEventArgs e)
{
OnSelected?.Invoke(Hierarchy);
}
private void MyHierarchy_OnOnSelected(InspectionElement selected)
{
OnSelected?.Invoke(selected);
Selection = selected;
}
}
}
+6
View File
@@ -0,0 +1,6 @@
using SkiaSharp;
namespace QuestPDF.Previewer
{
record PreviewPage(SKPicture Picture, float Width, float Height);
}
+8
View File
@@ -0,0 +1,8 @@
<Application x:Class="QuestPDF.Previewer.PreviewerApp"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="QuestPDF Document Preview">
<Application.Styles>
<FluentTheme Mode="Dark" />
</Application.Styles>
</Application>
+27
View File
@@ -0,0 +1,27 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace QuestPDF.Previewer
{
internal class PreviewerApp : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new PreviewerWindow()
{
DataContext = new PreviewerWindowViewModel()
};
}
base.OnFrameworkInitializationCompleted();
}
}
}
+199
View File
@@ -0,0 +1,199 @@
using System.Collections.ObjectModel;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
namespace QuestPDF.Previewer
{
class PreviewerControl : Control
{
private InteractiveCanvas InteractiveCanvas { get; set; } = new ();
public static readonly StyledProperty<ObservableCollection<PreviewPage>> PagesProperty =
AvaloniaProperty.Register<PreviewerControl, ObservableCollection<PreviewPage>>(nameof(Pages));
public ObservableCollection<PreviewPage>? Pages
{
get => GetValue(PagesProperty);
set => SetValue(PagesProperty, value);
}
public static readonly StyledProperty<float> CurrentScrollProperty = AvaloniaProperty.Register<PreviewerControl, float>(nameof(CurrentScroll));
public float CurrentScroll
{
get => GetValue(CurrentScrollProperty);
set => SetValue(CurrentScrollProperty, value);
}
public static readonly StyledProperty<float> ScrollViewportSizeProperty = AvaloniaProperty.Register<PreviewerControl, float>(nameof(ScrollViewportSize));
public float ScrollViewportSize
{
get => GetValue(ScrollViewportSizeProperty);
set => SetValue(ScrollViewportSizeProperty, value);
}
public static readonly StyledProperty<InspectionElement> HierarchyProperty = AvaloniaProperty.Register<PreviewerControl, InspectionElement>(nameof(Hierarchy));
public InspectionElement Hierarchy
{
get => GetValue(HierarchyProperty);
set => SetValue(HierarchyProperty, value);
}
public static readonly StyledProperty<InspectionElement> CurrentSelectionProperty = AvaloniaProperty.Register<PreviewerControl, InspectionElement>(nameof(CurrentSelection));
public InspectionElement CurrentSelection
{
get => GetValue(CurrentSelectionProperty);
set => SetValue(CurrentSelectionProperty, value);
}
public PreviewerControl()
{
PagesProperty.Changed.Subscribe(x =>
{
InteractiveCanvas.Pages = x.NewValue.Value;
InvalidateVisual();
});
CurrentScrollProperty.Changed.Subscribe(x =>
{
InteractiveCanvas.ScrollPercentY = x.NewValue.Value;
InvalidateVisual();
});
CurrentSelectionProperty.Changed.Subscribe(x =>
{
InteractiveCanvas.InspectionElement = CurrentSelection;
//InteractiveCanvas.ScrollToInspectionElement(CurrentSelection);
InvalidateVisual();
});
ClipToBounds = true;
PointerPressed += (sender, args) =>
{
var position = args.GetPosition(this);
InteractiveCanvas.SetActivePage((float)position.X, (float)position.Y);
var clickedPosition = InteractiveCanvas.FindClickedPointOnThePage((float)position.X, (float)position.Y);
if (clickedPosition != null)
FindHighlightedElement(clickedPosition.Value.pageNumber, clickedPosition.Value.x, clickedPosition.Value.y);
InvalidateVisual();
};
}
void FindHighlightedElement(int pageNumber, float x, float y)
{
var possible = FlattenHierarchy(Hierarchy, 0)
.Select(x =>
{
var location = x.element.Location.First(y => y.PageNumber == pageNumber);
return new
{
Element = x.element,
Level = x.level,
Size = location.Width * location.Height
};
})
.ToList();
var minSize = possible.Min(x => x.Size);
CurrentSelection = possible
.Where(x => Math.Abs(x.Size - minSize) < 1)
.OrderByDescending(x => x.Level)
.First()
.Element;
IEnumerable<(InspectionElement element, int level)> FlattenHierarchy(InspectionElement element, int level)
{
var location = element.Location.FirstOrDefault(x => x.PageNumber == pageNumber);
if (location == null)
yield break;
if (x < location.Left || location.Left + location.Width < x)
yield break;
if (y < location.Top || location.Top + location.Height < y)
yield break;
yield return (element, level);
foreach (var childIndex in Enumerable.Range(0, element.Children.Count))
foreach (var nestedChild in FlattenHierarchy(element.Children[childIndex], level + childIndex + 1))
yield return nestedChild;
}
}
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
base.OnPointerWheelChanged(e);
if ((e.KeyModifiers & KeyModifiers.Control) != 0)
{
var scaleFactor = 1 + e.Delta.Y / 10f;
var point = new Point(Bounds.Center.X, Bounds.Top) - e.GetPosition(this);
InteractiveCanvas.ZoomToPoint((float)point.X, -(float)point.Y, (float)scaleFactor);
}
if (e.KeyModifiers == KeyModifiers.None)
{
var translation = (float)e.Delta.Y * 25;
InteractiveCanvas.TranslateWithCurrentScale(0, -translation);
}
InvalidateVisual();
}
private bool IsMousePressed { get; set; }
private Vector MousePosition { get; set; }
protected override void OnPointerMoved(PointerEventArgs e)
{
base.OnPointerMoved(e);
if (IsMousePressed)
{
var currentPosition = e.GetPosition(this);
var translation = currentPosition - MousePosition;
InteractiveCanvas.TranslateWithCurrentScale((float)translation.X, -(float)translation.Y);
InvalidateVisual();
}
MousePosition = e.GetPosition(this);
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
IsMousePressed = true;
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
IsMousePressed = false;
}
public override void Render(DrawingContext context)
{
CurrentScroll = InteractiveCanvas.ScrollPercentY;
ScrollViewportSize = InteractiveCanvas.ScrollViewportSizeY;
InteractiveCanvas.Bounds = new Rect(0, 0, Bounds.Width, Bounds.Height);
context.Custom(InteractiveCanvas);
base.Render(context);
}
}
}
@@ -0,0 +1,18 @@
using SkiaSharp;
namespace QuestPDF.Previewer;
internal class DocumentSnapshot
{
public ICollection<PageSnapshot> Pages { get; set; }
public class PageSnapshot
{
public string Id { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public SKPicture Picture { get; set; }
}
}
+155
View File
@@ -0,0 +1,155 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:previewer="clr-namespace:QuestPDF.Previewer"
xmlns:visualBasic="clr-namespace:Microsoft.VisualBasic;assembly=Microsoft.VisualBasic.Core"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="QuestPDF.Previewer.PreviewerWindow"
x:DataType="previewer:PreviewerWindowViewModel"
x:CompileBindings="True"
WindowStartupLocation="CenterScreen"
ExtendClientAreaToDecorationsHint="true"
ExtendClientAreaTitleBarHeightHint="56"
Background="#555"
Icon="/Resources/Logo.png"
UseLayoutRounding="True"
Title="QuestPDF Document Preview">
<Window.Styles>
<Style Selector=".MainMenu TextBlock">
<Setter Property="Foreground" Value="#AFFF" />
<Setter Property="Margin" Value="0,0,16,0" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style Selector=".MainMenu TextBlock:pointerover">
<Setter Property="Foreground" Value="#FFF" />
<Setter Property="TextDecorations" Value="Underline" />
<Setter Property="Cursor" Value="Hand" />
</Style>
<Style Selector="Button.actions">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Padding" Value="12" />
<Setter Property="Margin" Value="-1" />
<Setter Property="Background" Value="transparent"/>
</Style>
<Style Selector="Button.actions Path">
<Setter Property="Fill" Value="#8FFF"/>
</Style>
<Style Selector="Button.active Path">
<Setter Property="Fill" Value="#FFFF"/>
</Style>
<Style Selector="Button:pointerover Path">
<Setter Property="Fill" Value="#FFFF"/>
</Style>
<Style Selector="Button:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="#333"/>
</Style>
</Window.Styles>
<Panel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="56" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="300" MaxWidth="500" Width="300" />
<ColumnDefinition Width="4" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Panel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" Background="#333">
<StackPanel Orientation="Horizontal">
<Image Source="/Resources/Logo.png" Width="32" Height="32" Margin="12,12,20,12"></Image>
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,0,4"
TextAlignment="Center" Text="QuestPDF Previewer" FontSize="16" Foreground="#FFF" FontWeight="SemiBold" />
<StackPanel Orientation="Horizontal" Classes="MainMenu">
<TextBlock Text="Getting Started" />
<TextBlock Text="Documentation" />
<TextBlock Text="GitHub" />
<TextBlock Text="Sponsor project" />
</StackPanel>
</StackPanel>
</StackPanel>
</Panel>
<Grid Grid.Row="1" Grid.Column="0" Background="#444">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Panel Grid.Row="0">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="16,8"
TextAlignment="Center" Text="Document hierarchy" Foreground="#DFFF" FontWeight="SemiBold" IsHitTestVisible="False" />
</Panel>
<Panel Grid.Row="1">
<ScrollViewer>
<previewer:MyHierarchy Hierarchy="{Binding Items}" Margin="-8,0,0,0" Selection="{Binding SelectedItem, Mode=TwoWay}"></previewer:MyHierarchy>
</ScrollViewer>
</Panel>
<Panel Grid.Row="3">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="16,8"
TextAlignment="Center" Text="Selected element properties" Foreground="#DFFF" FontWeight="SemiBold" IsHitTestVisible="False" />
</Panel>
<Panel Grid.Row="4">
<ItemsRepeater Items="{Binding SelectedItem.Metadata}" Margin="16, 2">
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="previewer:Metadata">
<Grid Margin="0, 4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Label}" Foreground="#AFFF" FontSize="12" />
<TextBlock Grid.Column="2" Text="{Binding Value}" Foreground="#AFFF" FontSize="12" />
</Grid>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Panel>
</Grid>
<GridSplitter Grid.Column="1" Grid.Row="1" Background="#444" ResizeDirection="Columns" />
<previewer:PreviewerControl Grid.Row="1" Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CurrentScroll="{Binding CurrentScroll, Mode=TwoWay}"
ScrollViewportSize="{Binding ScrollViewportSize, Mode=OneWayToSource}"
CurrentSelection="{Binding SelectedItem, Mode=TwoWay}"
Hierarchy="{Binding Items}"
Pages="{Binding Pages, Mode=OneWay}" />
<ScrollBar Grid.Row="1" Grid.Column="3"
Orientation="Vertical"
AllowAutoHide="False"
Minimum="0" Maximum="1"
IsVisible="{Binding VerticalScrollbarVisible, Mode=OneWay}"
Value="{Binding CurrentScroll, Mode=TwoWay}"
ViewportSize="{Binding ScrollViewportSize, Mode=OneWay}"></ScrollBar>
</Grid>
</Panel>
</Window>
@@ -0,0 +1,24 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace QuestPDF.Previewer
{
class PreviewerWindow : Window
{
public PreviewerWindow()
{
InitializeComponent();
}
protected override void OnClosed(EventArgs e)
{
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,177 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text.Json;
using Avalonia;
using ReactiveUI;
using Unit = System.Reactive.Unit;
using Avalonia.Threading;
namespace QuestPDF.Previewer
{
internal class PreviewerWindowViewModel : ReactiveObject
{
private ObservableCollection<PreviewPage> _pages = new();
public ObservableCollection<PreviewPage> Pages
{
get => _pages;
set => this.RaiseAndSetIfChanged(ref _pages, value);
}
private float _currentScroll;
public float CurrentScroll
{
get => _currentScroll;
set => this.RaiseAndSetIfChanged(ref _currentScroll, value);
}
private float _scrollViewportSize;
public float ScrollViewportSize
{
get => _scrollViewportSize;
set
{
this.RaiseAndSetIfChanged(ref _scrollViewportSize, value);
VerticalScrollbarVisible = value < 1;
}
}
private bool _verticalScrollbarVisible;
public bool VerticalScrollbarVisible
{
get => _verticalScrollbarVisible;
private set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _verticalScrollbarVisible, value));
}
public ReactiveCommand<Unit, Unit> ShowPdfCommand { get; }
public ReactiveCommand<Unit, Unit> ShowDocumentationCommand { get; }
public ReactiveCommand<Unit, Unit> SponsorProjectCommand { get; }
public PreviewerWindowViewModel()
{
CommunicationService.Instance.OnDocumentRefreshed += HandleUpdatePreview;
ShowPdfCommand = ReactiveCommand.Create(ShowPdf);
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/documentation/api-reference.html"));
SponsorProjectCommand = ReactiveCommand.Create(() => OpenLink("https://github.com/sponsors/QuestPDF"));
LoadItems();
}
private void ShowPdf()
{
var filePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.pdf");
Helpers.GeneratePdfFromDocumentSnapshots(filePath, Pages);
OpenLink(filePath);
}
private void OpenLink(string path)
{
using var openBrowserProcess = new Process
{
StartInfo = new()
{
UseShellExecute = true,
FileName = path
}
};
openBrowserProcess.Start();
}
private void HandleUpdatePreview(ICollection<PreviewPage> pages)
{
var oldPages = Pages;
Pages.Clear();
Pages = new ObservableCollection<PreviewPage>(pages);
foreach (var page in oldPages)
page.Picture.Dispose();
}
private InspectionElement _selectedItem;
public InspectionElement SelectedItem
{
get => _selectedItem;
set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
}
private InspectionElement items;
public InspectionElement Items
{
get => items;
set => this.RaiseAndSetIfChanged(ref items, value);
}
public void LoadItems()
{
var text = File.ReadAllText("hierarchy.json");
Items = JsonSerializer.Deserialize<InspectionElement>(text);
UpdateLevel(Items);
}
private void UpdateLevel(InspectionElement root)
{
var currentLevel = 1;
Traverse(root);
void Traverse(InspectionElement element)
{
element.Level = currentLevel;
currentLevel++;
element.Children.ForEach(Traverse);
currentLevel--;
}
}
}
public class InspectionElementLocation
{
public int PageNumber { get; set; }
public float Top { get; set; }
public float Left { get; set; }
public float Width { get; set; }
public float Height { get; set; }
}
public class Metadata
{
public string Label { get; set; }
public string Value { get; set; }
public Metadata(string label, string value)
{
Label = label;
Value = value;
}
}
public class InspectionElement
{
public string Element { get; set; }
public List<InspectionElementLocation> Location { get; set; }
public Dictionary<string, string> Properties { get; set; }
public List<InspectionElement> Children { get; set; }
public int Level { get; set; }
public string FontColor => Element == "DebugPointer" ? "#FFF" : "#AFFF";
public string Text => Element == "DebugPointer" ? Properties.First(x => x.Key == "Target").Value : Element;
public bool Expanded { get; set; }
public IList<Metadata> Metadata => ListMetadata().ToList();
public IEnumerable<Metadata> ListMetadata()
{
yield return new Metadata("Element name", Element);
yield return new Metadata("Position left", Location[0].Left.ToString("N2"));
yield return new Metadata("Position top", Location[0].Top.ToString("N2"));
yield return new Metadata("Width", Location[0].Width.ToString("N2"));
yield return new Metadata("Height", Location[0].Height.ToString("N2"));
foreach (var property in Properties)
yield return new Metadata(property.Key, property.Value);
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
using QuestPDF.Previewer;
var applicationPort = GetCommunicationPort();
CommunicationService.Instance.Start(applicationPort);
if(Application.Current?.ApplicationLifetime is ClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new PreviewerWindow()
{
DataContext = new PreviewerWindowViewModel()
};
desktop.MainWindow.Show();
desktop.Start(Array.Empty<string>());
return;
}
AppBuilder
.Configure(() => new PreviewerApp())
.UsePlatformDetect()
.UseReactiveUI()
.StartWithClassicDesktopLifetime(Array.Empty<string>());
static int GetCommunicationPort()
{
const int defaultApplicationPort = 12500;
var arguments = Environment.GetCommandLineArgs();
if (arguments.Length < 2)
return defaultApplicationPort;
return int.TryParse(arguments[1], out var port) ? port : defaultApplicationPort;
}
@@ -0,0 +1,58 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF.Previewer</PackageId>
<Version>2022.6.0</Version>
<PackAsTool>true</PackAsTool>
<ToolCommandName>questpdf-previewer</ToolCommandName>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
<PackageReleaseNotes>Initial release.</PackageReleaseNotes>
<LangVersion>10</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon>
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
<PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core previewer</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<OutputType>exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Resources\Logo.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Logo.png" />
<None Include="Resources\Logo.png">
<Pack>true</Pack>
<Visible>false</Visible>
<PackagePath>\</PackagePath>
</None>
<None Update="hierarchy.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.10" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
<PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.80.4" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="SkiaSharp" Version="2.80.4" />
</ItemGroup>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long
+23
View File
@@ -0,0 +1,23 @@
Install nuget locally (in directory where nupkg file is located)
```
dotnet tool install --global --add-source . QuestPDF.Previewer --global
```
Run on default port
```
questpdf-previewer
```
Run on custom port
```
questpdf-previewer 12500
```
Remove nuget locally
```
dotnet tool uninstall QuestPDF.Previewer --global
```
+28
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using SkiaSharp;
@@ -24,5 +25,32 @@ namespace QuestPDF.ReportSample
Latitude = Helpers.Random.NextDouble() * 180f - 90f
};
}
private static readonly ConcurrentDictionary<int, string> RomanNumeralCache = new ConcurrentDictionary<int, string>();
public static string FormatAsRomanNumeral(this int number)
{
if (number < 0 || number > 3999)
throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999");
return RomanNumeralCache.GetOrAdd(number, x =>
{
if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000);
if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900);
if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500);
if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400);
if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100);
if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90);
if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50);
if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40);
if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10);
if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9);
if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5);
if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4);
if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1);
return string.Empty;
});
}
}
}
@@ -30,12 +30,12 @@ namespace QuestPDF.ReportSample.Layouts
{
column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
{
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
});
column.Item().SkipOnce().Padding(5).Row(row =>
{
row.RelativeItem(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeItem(2).Text("SECONDARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
});
});
@@ -1,3 +1,4 @@
using System;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -47,7 +48,7 @@ namespace QuestPDF.ReportSample.Layouts
container.Border(0.75f).BorderColor(Colors.Grey.Medium).Grid(grid =>
{
grid.Columns(6);
grid.Item().LabelCell().Text("Date");
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty);
grid.Item().LabelCell().Text("Location");
@@ -23,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts
decoration
.Before()
.PaddingBottom(5)
.Text(Model.Title, Typography.Headline);
.Text(Model.Title)
.Style(Typography.Headline);
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
{
@@ -69,7 +70,7 @@ namespace QuestPDF.ReportSample.Layouts
{
if (model.PhotoCount == 0)
{
container.Text("No photos", Typography.Normal);
container.Text("No photos").Style(Typography.Normal);
return;
}
@@ -39,9 +39,9 @@ namespace QuestPDF.ReportSample.Layouts
page.Footer().AlignCenter().Text(text =>
{
text.CurrentPageNumber();
text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
text.Span(" / ");
text.TotalPages();
text.TotalPages().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
});
});
}
@@ -54,8 +54,8 @@ namespace QuestPDF.ReportSample.Layouts
{
row.Spacing(50);
row.RelativeItem().PaddingTop(-10).Text(Model.Title, Typography.Title);
row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
row.RelativeItem().PaddingTop(-10).Text(Model.Title).Style(Typography.Title);
row.ConstantItem(90).Hyperlink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
});
column.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts
{
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
text.Span($"{field.Label}: ").SemiBold();
text.Span(field.Value);
});
}
@@ -88,14 +88,14 @@ namespace QuestPDF.ReportSample.Layouts
column.Item().PageBreak();
foreach (var section in Model.Sections)
column.Item().Location(section.Title).Component(new SectionTemplate(section));
column.Item().Section(section.Title).Component(new SectionTemplate(section));
column.Item().PageBreak();
column.Item().Location("Photos");
column.Item().Section("Photos");
foreach (var photo in Model.Photos)
column.Item().Component(new PhotoTemplate(photo));
});
}
}
}
}
@@ -1,6 +1,9 @@
using System.Collections.Generic;
using System.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.ReportSample.Layouts
{
@@ -21,7 +24,8 @@ namespace QuestPDF.ReportSample.Layouts
decoration
.Before()
.PaddingBottom(5)
.Text("Table of contents", Typography.Headline);
.Text("Table of contents")
.Style(Typography.Headline);
decoration.Content().Column(column =>
{
@@ -38,13 +42,37 @@ namespace QuestPDF.ReportSample.Layouts
private void DrawLink(IContainer container, int number, string locationName)
{
container
.InternalLink(locationName)
.SectionLink(locationName)
.Row(row =>
{
row.ConstantItem(25).Text($"{number}.");
row.RelativeItem().Text(locationName);
row.ConstantItem(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
row.ConstantItem(20).Text($"{number}.");
row.AutoItem().Text(locationName);
row.RelativeItem().PaddingHorizontal(2).AlignBottom().TranslateY(-3).Height(1).Canvas((canvas, space) =>
{
// best to statically cache
using var paint = new SKPaint
{
StrokeWidth = space.Height,
PathEffect = SKPathEffect.CreateDash(new float[] { 1, 3 }, 0)
};
canvas.DrawLine(0, 0, space.Width, 0, paint);
});
row.AutoItem().Text(text =>
{
text.BeginPageNumberOfSection(locationName);
text.Span(" - ");
text.EndPageNumberOfSection(locationName);
var lengthStyle = TextStyle.Default.FontColor(Colors.Grey.Medium);
text.Span(" (").Style(lengthStyle);
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
text.Span(")").Style(lengthStyle);
});
});
}
}
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>8</LangVersion>
<RootNamespace>QuestPDF.ReportSample</RootNamespace>
@@ -12,7 +12,7 @@
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="SkiaSharp" Version="2.80.4" />
</ItemGroup>
<ItemGroup>

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

+4 -4
View File
@@ -6,8 +6,8 @@ namespace QuestPDF.ReportSample
{
public static class Typography
{
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f);
public static TextStyle Title => TextStyle.Default.FontFamily(Fonts.Calibri).FontColor(Colors.Blue.Darken3).FontSize(26).Black();
public static TextStyle Headline => TextStyle.Default.FontFamily(Fonts.Calibri).FontColor(Colors.Blue.Medium).FontSize(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontFamily(Fonts.Verdana).FontColor(Colors.Black).FontSize(10).LineHeight(1.2f);
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class ExternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
public void Measure() => SimpleContainerTests.Measure<Hyperlink>();
// TODO: consider tests for the Draw method
}
+146
View File
@@ -0,0 +1,146 @@
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Drawing;
using SkiaSharp;
using static SkiaSharp.SKFontStyleSlant;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class FontStyleSetTests
{
private void ExpectComparisonOrder(SKFontStyle target, SKFontStyle[] styles)
{
for (var i = 0; i < styles.Length - 1; i++)
{
var currentStyle = styles[i];
var nextStyle = styles[i + 1];
FontStyleSet.IsBetterMatch(target, currentStyle, nextStyle).Should().BeTrue();
FontStyleSet.IsBetterMatch(target, nextStyle, currentStyle).Should().BeFalse();
}
}
[Test]
public void FontStyleSet_IsBetterMatch_CondensedWidth()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(500, 4, Upright),
new SKFontStyle(500, 3, Upright),
new SKFontStyle(500, 6, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ExpandedWidth()
{
var styles = new[]
{
new SKFontStyle(500, 6, Upright),
new SKFontStyle(500, 7, Upright),
new SKFontStyle(500, 8, Upright),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 6, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ItalicSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Italic),
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Italic), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ObliqueSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Italic),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Oblique), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_UprightSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Italic)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ThinWeight()
{
var styles = new[]
{
new SKFontStyle(300, 5, Upright),
new SKFontStyle(200, 5, Upright),
new SKFontStyle(100, 5, Upright),
new SKFontStyle(400, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(300, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_RegularWeight()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(300, 5, Upright),
new SKFontStyle(100, 5, Upright),
new SKFontStyle(600, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(400, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_BoldWeight()
{
var styles = new[]
{
new SKFontStyle(600, 5, Upright),
new SKFontStyle(700, 5, Upright),
new SKFontStyle(800, 5, Upright),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(600, 5, Upright), styles);
}
[Test]
public void FontStyleSet_RespectsPriority()
{
var styles = new[]
{
new SKFontStyle(600, 5, Italic),
new SKFontStyle(600, 6, Upright),
new SKFontStyle(500, 6, Italic)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class InternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
// TODO: consider tests for the Draw method
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class InternalLocationTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
// TODO: consider tests for the Draw method
}
+1 -1
View File
@@ -10,7 +10,7 @@
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="SkiaSharp" Version="2.80.4" />
</ItemGroup>
<ItemGroup>
+6 -6
View File
@@ -1,4 +1,5 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
using SkiaSharp;
@@ -10,7 +11,6 @@ namespace QuestPDF.UnitTests.TestEngine
public Action<float> RotateFunc { get; set; }
public Action<float, float> ScaleFunc { get; set; }
public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
public Action<Position, Size, string> DrawRectFunc { get; set; }
public void Translate(Position vector) => TranslateFunc(vector);
@@ -18,11 +18,11 @@ namespace QuestPDF.UnitTests.TestEngine
public void Scale(float scaleX, float scaleY) => ScaleFunc(scaleX, scaleY);
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style) => throw new NotImplementedException();
public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
public void DrawLocation(string locationName) => throw new NotImplementedException();
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
public void DrawSection(string sectionName) => throw new NotImplementedException();
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine.Operations;
using SkiaSharp;
@@ -15,11 +16,11 @@ namespace QuestPDF.UnitTests.TestEngine
public void Scale(float scaleX, float scaleY) => Operations.Add(new CanvasScaleOperation(scaleX, scaleY));
public void DrawRectangle(Position vector, Size size, string color) => Operations.Add(new CanvasDrawRectangleOperation(vector, size, color));
public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style) => throw new NotImplementedException();
public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
public void DrawLocation(string locationName) => throw new NotImplementedException();
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
public void DrawSection(string sectionName) => throw new NotImplementedException();
}
}
-18
View File
@@ -82,19 +82,6 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.Color, color, "Draw rectangle: color");
},
DrawTextFunc = (text, position, style) =>
{
var expected = GetExpected<CanvasDrawTextOperation>();
Assert.AreEqual(expected.Text, text);
Assert.AreEqual(expected.Position.X, position.X, "Draw text: X");
Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
},
DrawImageFunc = (image, position, size) =>
{
var expected = GetExpected<CanvasDrawImageOperation>();
@@ -201,11 +188,6 @@ namespace QuestPDF.UnitTests.TestEngine
return AddOperation(new CanvasDrawRectangleOperation(position, size, color));
}
public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style)
{
return AddOperation(new CanvasDrawTextOperation(text, position, style));
}
public TestPlan ExpectCanvasDrawImage(Position position, Size size)
{
return AddOperation(new CanvasDrawImageOperation(position, size));
+17
View File
@@ -8,6 +8,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.UnitTests", "Quest
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Examples", "QuestPDF.Examples\QuestPDF.Examples.csproj", "{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer.Examples", "QuestPDF.Previewer.Examples\QuestPDF.Previewer.Examples.csproj", "{CA413A39-038F-4A9F-B56F-0E5413B6B158}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{73123649-216A-47B4-BFB2-DADF13FBE75D}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer", "QuestPDF.Previewer\QuestPDF.Previewer.csproj", "{B2FF6003-3A45-4A78-A85D-B86C7F01D054}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +39,13 @@ Global
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.Build.0 = Release|Any CPU
{CA413A39-038F-4A9F-B56F-0E5413B6B158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA413A39-038F-4A9F-B56F-0E5413B6B158}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA413A39-038F-4A9F-B56F-0E5413B6B158}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA413A39-038F-4A9F-B56F-0E5413B6B158}.Release|Any CPU.Build.0 = Release|Any CPU
{B2FF6003-3A45-4A78-A85D-B86C7F01D054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2FF6003-3A45-4A78-A85D-B86C7F01D054}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2FF6003-3A45-4A78-A85D-B86C7F01D054}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2FF6003-3A45-4A78-A85D-B86C7F01D054}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
-6
View File
@@ -1,6 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("QuestPDF.UnitTests")]
[assembly: InternalsVisibleTo("QuestPDF.Examples")]
[assembly: InternalsVisibleTo("QuestPDF.ReportSample")]
+1
View File
@@ -16,6 +16,7 @@ namespace QuestPDF.Drawing
var container = new Container();
container
.DebugPointer("Document")
.Column(column =>
{
Pages
+253 -12
View File
@@ -1,7 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Drawing.Proxy;
using QuestPDF.Elements;
@@ -10,6 +12,8 @@ using QuestPDF.Elements.Text.Items;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace QuestPDF.Drawing
{
@@ -17,19 +21,30 @@ namespace QuestPDF.Drawing
{
internal static void GeneratePdf(Stream stream, IDocument document)
{
CheckIfStreamIsCompatible(stream);
var metadata = document.GetMetadata();
var writeOnlyStream = new WriteOnlyStream(stream);
var canvas = new PdfCanvas(writeOnlyStream, metadata);
var canvas = new PdfCanvas(stream, metadata);
RenderDocument(canvas, document);
}
internal static void GenerateXps(Stream stream, IDocument document)
{
CheckIfStreamIsCompatible(stream);
var metadata = document.GetMetadata();
var writeOnlyStream = new WriteOnlyStream(stream);
var canvas = new XpsCanvas(writeOnlyStream, metadata);
var canvas = new XpsCanvas(stream, metadata);
RenderDocument(canvas, document);
}
private static void CheckIfStreamIsCompatible(Stream stream)
{
if (!stream.CanWrite)
throw new ArgumentException("The library requires a Stream object with the 'write' capability available (the CanWrite flag). Please consider using the MemoryStream class.");
if (!stream.CanSeek)
throw new ArgumentException("The library requires a Stream object with the 'seek' capability available (the CanSeek flag). Please consider using the MemoryStream class.");
}
internal static ICollection<byte[]> GenerateImages(IDocument document)
{
@@ -40,7 +55,14 @@ namespace QuestPDF.Drawing
return canvas.Images;
}
private static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
internal static ICollection<PreviewerPicture> GeneratePreviewerPictures(IDocument document)
{
var canvas = new SkiaPictureCanvas();
RenderDocument(canvas, document);
return canvas.Pictures;
}
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
where TCanvas : ICanvas, IRenderingCanvas
{
var container = new DocumentContainer();
@@ -57,9 +79,218 @@ namespace QuestPDF.Drawing
ApplyCaching(content);
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
if (metadata.ApplyInspection)
ApplyInspection(content);
RenderPass(pageContext, canvas, content, metadata, debuggingState);
if (metadata.ApplyInspection)
{
var x = TraverseStatisticsToJson(content);
var y = JsonSerializer.Serialize(x);
}
}
internal static string TraverseStructure(Element container)
{
var builder = new StringBuilder();
var nestingLevel = 0;
Traverse(container);
return builder.ToString();
void Traverse(Element item)
{
var indent = new string(' ', nestingLevel * 4);
var title = item.GetType().Name;
builder.AppendLine(indent + title);
nestingLevel++;
foreach (var child in item.GetChildren())
Traverse(child);
nestingLevel--;
}
}
internal static string TraverseStatistics(Element container, int pageNumber)
{
var builder = new StringBuilder();
var nestingLevel = 0;
Traverse(container);
return builder.ToString();
void Traverse(Element item)
{
if (item is DebuggingProxy or CacheProxy or Container)
{
Traverse(item.GetChildren().First());
return;
}
var inspectionItem = item as InspectionProxy;
if (inspectionItem == null)
{
var children = item.GetChildren().ToList();
if (children.Count > 1)
nestingLevel++;
children.ForEach(Traverse);
if (children.Count > 1)
nestingLevel--;
return;
}
if (!inspectionItem.Statistics.ContainsKey(pageNumber))
return;
var statistics = inspectionItem.Statistics[pageNumber];
var indent = new string(' ', nestingLevel * 4);
var title = statistics.Element.GetType().Name;
builder.AppendLine(indent + title);
builder.AppendLine(indent + new string('-', title.Length));
builder.AppendLine(indent + "Size: " + statistics.Size);
builder.AppendLine(indent + "Position: " + statistics.Position);
foreach (var configuration in DebuggingState.GetElementConfiguration(statistics.Element))
builder.AppendLine(indent + configuration);
builder.AppendLine();
Traverse(inspectionItem.Child);
}
}
internal static InspectionElement TraverseStatisticsToJson(Element container)
{
return Traverse(container);
InspectionElement? Traverse(Element item)
{
InspectionElement? result = null;
Element currentItem = item;
while (true)
{
if (currentItem is InspectionProxy proxy)
{
if (proxy.Child.GetType() == typeof(Container))
{
currentItem = proxy.Child;
continue;
}
var statistics = GetInspectionElement(proxy);
if (statistics == null)
return null;
if (result == null)
{
result = statistics;
}
else
{
result.Children.Add(statistics);
}
currentItem = proxy.Child;
}
else
{
var children = currentItem.GetChildren().ToList();
if (children.Count == 0)
{
return result;
}
else if (children.Count == 1)
{
currentItem = children.First();
continue;
}
else
{
children
.Select(Traverse)
.Where(x => x != null)
.ToList()
.ForEach(result.Children.Add);
return result;
}
}
}
}
static InspectionElement? GetInspectionElement(InspectionProxy inspectionProxy)
{
var locations = inspectionProxy
.Statistics
.Keys
.Select(x =>
{
var statistics = inspectionProxy.Statistics[x];
return new InspectionElementLocation
{
PageNumber = x,
Top = statistics.Position.Y,
Left = statistics.Position.X,
Width = statistics.Size.Width,
Height = statistics.Size.Height,
};
})
.ToList();
return new InspectionElement
{
Element = inspectionProxy.Child.GetType().Name,
Location = locations,
Properties = GetElementConfiguration(inspectionProxy.Child),
Children = new List<InspectionElement>()
};
}
static Dictionary<string, string> GetElementConfiguration(IElement element)
{
return element
.GetType()
.GetProperties()
.Select(x => new
{
Property = x.Name.PrettifyName(),
Value = x.GetValue(element)
})
.Where(x => !(x.Value is IElement))
.Where(x => x.Value is string || !(x.Value is IEnumerable))
.Where(x => !(x.Value is TextStyle))
.ToDictionary(x => x.Property, x => FormatValue(x.Value));
string FormatValue(object value)
{
const int maxLength = 100;
var text = value?.ToString() ?? "-";
if (text.Length < maxLength)
return text;
return text.AsSpan(0, maxLength).ToString() + "...";
}
}
}
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
where TCanvas : ICanvas, IRenderingCanvas
{
@@ -117,11 +348,10 @@ namespace QuestPDF.Drawing
$"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " +
$"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
$"Please analyze your documents structure to detect this element and fix its size constraints.";
throw new DocumentLayoutException(message)
{
ElementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode."
};
var elementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode.";
throw new DocumentLayoutException(message, elementTrace);
}
}
@@ -140,13 +370,21 @@ namespace QuestPDF.Drawing
content.VisitChildren(x =>
{
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
x.CreateProxy(y => y is ElementProxy ? y : new DebuggingProxy(debuggingState, y));
});
return debuggingState;
}
private static void ApplyInspection(Container content)
{
content.VisitChildren(x =>
{
x.CreateProxy(y => y is ElementProxy ? y : new InspectionProxy(y));
});
}
private static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
{
if (content == null)
return;
@@ -168,6 +406,9 @@ namespace QuestPDF.Drawing
return;
}
if (content is DynamicHost dynamicHost)
dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
var targetTextStyle = documentDefaultTextStyle;
if (content is DefaultTextStyle defaultTextStyleElement)
+1
View File
@@ -26,6 +26,7 @@ namespace QuestPDF.Drawing
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
public bool ApplyInspection { get; set; } = System.Diagnostics.Debugger.IsAttached;
public static DocumentMetadata Default => new DocumentMetadata();
}
@@ -4,17 +4,7 @@ namespace QuestPDF.Drawing.Exceptions
{
public class DocumentComposeException : Exception
{
public DocumentComposeException()
{
}
public DocumentComposeException(string message) : base(message)
{
}
public DocumentComposeException(string message, Exception inner) : base(message, inner)
internal DocumentComposeException(string message) : base(message)
{
}
@@ -4,17 +4,7 @@ namespace QuestPDF.Drawing.Exceptions
{
public class DocumentDrawingException : Exception
{
public DocumentDrawingException()
{
}
public DocumentDrawingException(string message) : base(message)
{
}
public DocumentDrawingException(string message, Exception inner) : base(message, inner)
internal DocumentDrawingException(string message, Exception inner) : base(message, inner)
{
}
@@ -4,21 +4,11 @@ namespace QuestPDF.Drawing.Exceptions
{
public class DocumentLayoutException : Exception
{
public string ElementTrace { get; set; }
public DocumentLayoutException()
{
}
public string? ElementTrace { get; }
public DocumentLayoutException(string message) : base(message)
internal DocumentLayoutException(string message, string? elementTrace = null) : base(message)
{
}
public DocumentLayoutException(string message, Exception inner) : base(message, inner)
{
ElementTrace = elementTrace;
}
}
}
@@ -0,0 +1,36 @@
using System;
namespace QuestPDF.Drawing.Exceptions
{
public class InitializationException : Exception
{
internal InitializationException(string documentType, Exception innerException) : base(CreateMessage(documentType, innerException.Message), innerException)
{
}
private static string CreateMessage(string documentType, string innerExceptionMessage)
{
var (libraryName, nugetConvention) = GetLibraryName();
return $"Cannot create the {documentType} document using the {libraryName} library. " +
$"This exception usually means that, on your operating system where you run the application, {libraryName} requires installing additional dependencies. " +
$"Such dependencies are available as additional nuget packages, for example {nugetConvention}.Linux. " +
$"Some operating systems may require installing multiple nugets, e.g. MacOS may need both {nugetConvention}.macOS and {nugetConvention}.Linux." +
$"Please refer to the {libraryName} documentation for more details. " +
$"Also, please consult the inner exception that has been originally thrown by the dependency library.";
(string GetLibraryName, string nugetConvention) GetLibraryName()
{
if (innerExceptionMessage.Contains("libSkiaSharp"))
return ("SkiaSharp", "SkiaSharp.NativeAssets.Linux");
if (innerExceptionMessage.Contains("libHarfBuzzSharp"))
return ("HarfBuzzSharp", "HarfBuzzSharp.NativeAssets");
// default
return ("SkiaSharp-related", "*.NativeAssets.Linux");
}
}
}
}
+117 -19
View File
@@ -1,66 +1,164 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using HarfBuzzSharp;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Drawing
{
public static class FontManager
{
private static ConcurrentDictionary<string, SKTypeface> Typefaces = new ConcurrentDictionary<string, SKTypeface>();
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new();
private static ConcurrentDictionary<object, SKPaint> FontPaints = new();
private static ConcurrentDictionary<string, SKPaint> ColorPaints = new();
private static ConcurrentDictionary<object, Font> ShaperFonts = new();
private static ConcurrentDictionary<object, SKFont> Fonts = new();
private static ConcurrentDictionary<object, TextShaper> TextShapers = new();
private static void RegisterFontType(SKData fontData, string? customName = null)
{
foreach (var index in Enumerable.Range(0, 256))
{
var typeface = SKTypeface.FromData(fontData, index);
if (typeface == null)
break;
var typefaceName = customName ?? typeface.FamilyName;
var fontStyleSet = StyleSets.GetOrAdd(typefaceName, _ => new FontStyleSet());
fontStyleSet.Add(typeface);
}
}
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFont(Stream stream) method.")]
public static void RegisterFontType(string fontName, Stream stream)
{
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
RegisterFontType(fontData, customName: fontName);
}
public static void RegisterFont(Stream stream)
{
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
}
internal static SKPaint ColorToPaint(this string color)
{
return ColorPaint.GetOrAdd(color, Convert);
return ColorPaints.GetOrAdd(color, Convert);
static SKPaint Convert(string color)
{
return new SKPaint
{
Color = SKColor.Parse(color)
Color = SKColor.Parse(color),
IsAntialias = true
};
}
}
internal static SKPaint ToPaint(this TextStyle style)
{
return Paints.GetOrAdd(style.Key, key => Convert(style));
return FontPaints.GetOrAdd(style.PaintKey, key => Convert(style));
static SKPaint Convert(TextStyle style)
{
return new SKPaint
{
Color = SKColor.Parse(style.Color),
Typeface = GetTypeface(style),
TextSize = (style.Size ?? 12),
TextEncoding = SKTextEncoding.Utf32
TextSize = (style.Size ?? 12) * GetTextScale(style),
IsAntialias = true,
};
}
static SKTypeface GetTypeface(TextStyle style)
{
if (Typefaces.TryGetValue(style.FontType, out var result))
return result;
var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
// superscript and subscript use slightly bolder font to match visually line thickness
if (style.FontPosition is FontPosition.Superscript or FontPosition.Subscript)
{
var weightValue = (int)weight;
weightValue = Math.Min(weightValue + 100, 1000);
weight = (SKFontStyleWeight) (weightValue);
}
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant);
if (StyleSets.TryGetValue(style.FontFamily, out var fontStyleSet))
return fontStyleSet.Match(fontStyle);
var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontFamily, fontStyle);
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
if (fontFromDefaultSource != null)
return fontFromDefaultSource;
throw new ArgumentException(
$"The typeface '{style.FontFamily}' could not be found. " +
$"Please consider the following options: " +
$"1) install the font on your operating system or execution environment. " +
$"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method.");
}
static float GetTextScale(TextStyle style)
{
return style.FontPosition switch
{
FontPosition.Normal => 1f,
FontPosition.Subscript => 0.625f,
FontPosition.Superscript => 0.625f,
_ => throw new ArgumentOutOfRangeException()
};
}
}
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.NormalPosition().ToPaint().FontMetrics);
}
internal static Font ToShaperFont(this TextStyle style)
{
return ShaperFonts.GetOrAdd(style.PaintKey, _ =>
{
var typeface = style.ToPaint().Typeface;
using var harfBuzzBlob = typeface.OpenStream(out var ttcIndex).ToHarfBuzzBlob();
using var face = new Face(harfBuzzBlob, ttcIndex)
{
Index = ttcIndex,
UnitsPerEm = typeface.UnitsPerEm,
GlyphCount = typeface.GlyphCount
};
var font = new Font(face);
font.SetScale(TextShaper.FontShapingScale, TextShaper.FontShapingScale);
font.SetFunctionsOpenType();
return font;
});
}
internal static TextShaper ToTextShaper(this TextStyle style)
{
return TextShapers.GetOrAdd(style.PaintKey, _ => new TextShaper(style));
}
internal static SKFont FoFont(this TextStyle style)
{
return Fonts.GetOrAdd(style.PaintKey, _ => style.ToPaint().ToFont());
}
}
}
+132
View File
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class FontStyleSet
{
private ConcurrentDictionary<SKFontStyle, SKTypeface> Styles { get; } = new();
public void Add(SKTypeface typeface)
{
var style = typeface.FontStyle;
Styles.AddOrUpdate(style, _ => typeface, (_, _) => typeface);
}
public SKTypeface? Match(SKFontStyle target)
{
SKFontStyle? bestStyle = null;
SKTypeface? bestTypeface = null;
foreach (var entry in Styles)
{
if (IsBetterMatch(target, entry.Key, bestStyle))
{
bestStyle = entry.Key;
bestTypeface = entry.Value;
}
}
return bestTypeface;
}
private static Dictionary<SKFontStyleSlant, List<SKFontStyleSlant>> SlantFallbacks = new()
{
{ SKFontStyleSlant.Italic, new() { SKFontStyleSlant.Italic, SKFontStyleSlant.Oblique, SKFontStyleSlant.Upright } },
{ SKFontStyleSlant.Oblique, new() { SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic, SKFontStyleSlant.Upright } },
{ SKFontStyleSlant.Upright, new() { SKFontStyleSlant.Upright, SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic } },
};
// Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm
internal static bool IsBetterMatch(SKFontStyle? target, SKFontStyle? a, SKFontStyle? b)
{
// A font is better than no font
if (b == null)
return true;
if (a == null)
return false;
// First check font width
// For normal and condensed widths prefer smaller widths
// For expanded widths prefer larger widths
if (target.Width <= (int)SKFontStyleWidth.Normal)
{
if (a.Width <= target.Width && b.Width > target.Width)
return true;
if (a.Width > target.Width && b.Width <= target.Width)
return false;
}
else
{
if (a.Width >= target.Width && b.Width < target.Width)
return true;
if (a.Width < target.Width && b.Width >= target.Width)
return false;
}
// Prefer closest match
var widthDifferenceA = Math.Abs(a.Width - target.Width);
var widthDifferenceB = Math.Abs(b.Width - target.Width);
if (widthDifferenceA < widthDifferenceB)
return true;
if (widthDifferenceB < widthDifferenceA)
return false;
// Prefer closest slant based on provided fallback list
var slantFallback = SlantFallbacks[target.Slant];
var slantIndexA = slantFallback.IndexOf(a.Slant);
var slantIndexB = slantFallback.IndexOf(b.Slant);
if (slantIndexA < slantIndexB)
return true;
if (slantIndexB < slantIndexA)
return false;
// Check weight last
// For thin (<400) weights, prefer thinner weights
// For regular (400-500) weights, prefer other regular weights, then use rule for thin or bold
// For bold (>500) weights, prefer thicker weights
// Behavior for values other than multiples of 100 is not given in the specification
if (target.Weight >= 400 && target.Weight <= 500)
{
if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500))
return true;
if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500))
return false;
}
if (target.Weight < 450)
{
if (a.Weight <= target.Weight && b.Weight > target.Weight)
return true;
if (a.Weight > target.Weight && b.Weight <= target.Weight)
return false;
}
else
{
if (a.Weight >= target.Weight && b.Weight < target.Weight)
return true;
if (a.Weight < target.Weight && b.Weight >= target.Weight)
return false;
}
// Prefer closest weight
var weightDifferenceA = Math.Abs(a.Weight - target.Weight);
var weightDifferenceB = Math.Abs(b.Weight - target.Weight);
return weightDifferenceA < weightDifferenceB;
}
}
}
+4 -4
View File
@@ -41,7 +41,7 @@ namespace QuestPDF.Drawing
}
public void DrawText(string text, Position position, TextStyle style)
public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style)
{
}
@@ -51,17 +51,17 @@ namespace QuestPDF.Drawing
}
public void DrawExternalLink(string url, Size size)
public void DrawHyperlink(string url, Size size)
{
}
public void DrawLocationLink(string locationName, Size size)
public void DrawSectionLink(string sectionName, Size size)
{
}
public void DrawLocation(string locationName)
public void DrawSection(string sectionName)
{
}
+17 -3
View File
@@ -1,4 +1,6 @@
using System.IO;
using System;
using System.IO;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Helpers;
using SkiaSharp;
@@ -7,11 +9,23 @@ namespace QuestPDF.Drawing
internal class PdfCanvas : SkiaDocumentCanvasBase
{
public PdfCanvas(Stream stream, DocumentMetadata documentMetadata)
: base(SKDocument.CreatePdf(stream, MapMetadata(documentMetadata)))
: base(CreatePdf(stream, documentMetadata))
{
}
private static SKDocument CreatePdf(Stream stream, DocumentMetadata documentMetadata)
{
try
{
return SKDocument.CreatePdf(stream, MapMetadata(documentMetadata));
}
catch (TypeInitializationException exception)
{
throw new InitializationException("PDF", exception);
}
}
private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata)
{
return new SKDocumentPdfMetadata
+25 -25
View File
@@ -94,36 +94,36 @@ namespace QuestPDF.Drawing.Proxy
item.Stack.ToList().ForEach(Traverse);
nestingLevel--;
}
static IEnumerable<string> GetElementConfiguration(IElement element)
{
if (element is DebugPointer)
return Enumerable.Empty<string>();
}
internal static IEnumerable<string> GetElementConfiguration(IElement element)
{
if (element is DebugPointer)
return Enumerable.Empty<string>();
return element
.GetType()
.GetProperties()
.Select(x => new
{
Property = x.Name.PrettifyName(),
Value = x.GetValue(element)
})
.Where(x => !(x.Value is IElement))
.Where(x => x.Value is string || !(x.Value is IEnumerable))
.Where(x => !(x.Value is TextStyle))
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
string FormatValue(object value)
return element
.GetType()
.GetProperties()
.Select(x => new
{
const int maxLength = 100;
Property = x.Name.PrettifyName(),
Value = x.GetValue(element)
})
.Where(x => !(x.Value is IElement))
.Where(x => x.Value is string || !(x.Value is IEnumerable))
.Where(x => !(x.Value is TextStyle))
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
string FormatValue(object value)
{
const int maxLength = 100;
var text = value?.ToString() ?? "-";
var text = value?.ToString() ?? "-";
if (text.Length < maxLength)
return text;
if (text.Length < maxLength)
return text;
return text.AsSpan(0, maxLength).ToString() + "...";
}
return text.AsSpan(0, maxLength).ToString() + "...";
}
}
}
+35
View File
@@ -0,0 +1,35 @@
using System.Collections;
using System.Collections.Generic;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing.Proxy
{
internal class InspectionProxy : ElementProxy
{
public Dictionary<int, InspectionStateItem> Statistics { get; set; } = new();
public InspectionProxy(Element child)
{
Child = child;
}
internal override void Draw(Size availableSpace)
{
if (Canvas is SkiaCanvasBase canvas)
{
var matrix = canvas.Canvas.TotalMatrix;
var inspectionItem = new InspectionStateItem
{
Element = Child,
Position = new Position(matrix.TransX, matrix.TransY),
Size = availableSpace
};
Statistics[PageContext.CurrentPage] = inspectionItem;
}
base.Draw(availableSpace);
}
}
}
@@ -0,0 +1,7 @@
namespace QuestPDF.Drawing.Proxy
{
public class InspectionState
{
}
}
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing.Proxy
{
internal class InspectionStateItem
{
public Element Element { get; internal set; }
public Size Size { get; internal set; }
public Position Position { get; internal set; }
}
internal class InspectionElementLocation
{
public int PageNumber { get; internal set; }
public float Top { get; set; }
public float Left { get; set; }
public float Width { get; set; }
public float Height { get; set; }
}
internal class InspectionElement
{
public string Element { get; internal set; }
public ICollection<InspectionElementLocation> Location { get; internal set; }
public Dictionary<string, string> Properties { get; internal set; }
public ICollection<InspectionElement> Children { get; set; }
}
}
+8 -7
View File
@@ -1,5 +1,6 @@
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Drawing
{
@@ -27,9 +28,9 @@ namespace QuestPDF.Drawing
Canvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint);
}
public void DrawText(string text, Position vector, TextStyle style)
public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style)
{
Canvas.DrawText(text, vector.X, vector.Y, style.ToPaint());
Canvas.DrawText(skTextBlob, position.X, position.Y, style.ToPaint());
}
public void DrawImage(SKImage image, Position vector, Size size)
@@ -37,19 +38,19 @@ namespace QuestPDF.Drawing
Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
}
public void DrawExternalLink(string url, Size size)
public void DrawHyperlink(string url, Size size)
{
Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
}
public void DrawLocationLink(string locationName, Size size)
public void DrawSectionLink(string sectionName, Size size)
{
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName);
}
public void DrawLocation(string locationName)
public void DrawSection(string sectionName)
{
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName);
}
public void Rotate(float angle)
+4 -4
View File
@@ -6,16 +6,16 @@ namespace QuestPDF.Drawing
{
internal class SkiaDocumentCanvasBase : SkiaCanvasBase
{
private SKDocument Document { get; }
private SKDocument? Document { get; }
public SkiaDocumentCanvasBase(SKDocument document)
protected SkiaDocumentCanvasBase(SKDocument document)
{
Document = document;
}
~SkiaDocumentCanvasBase()
{
Document.Dispose();
Document?.Dispose();
}
public override void BeginDocument()
@@ -25,7 +25,7 @@ namespace QuestPDF.Drawing
public override void EndDocument()
{
Canvas.Dispose();
Canvas?.Dispose();
Document.Close();
Document.Dispose();
+55
View File
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
public class PreviewerPicture
{
public SKPicture Picture { get; set; }
public Size Size { get; set; }
public PreviewerPicture(SKPicture picture, Size size)
{
Picture = picture;
Size = size;
}
}
internal class SkiaPictureCanvas : SkiaCanvasBase
{
private SKPictureRecorder? PictureRecorder { get; set; }
private Size? CurrentPageSize { get; set; }
public ICollection<PreviewerPicture> Pictures { get; } = new List<PreviewerPicture>();
public override void BeginDocument()
{
Pictures.Clear();
}
public override void BeginPage(Size size)
{
CurrentPageSize = size;
PictureRecorder = new SKPictureRecorder();
Canvas = PictureRecorder.BeginRecording(new SKRect(0, 0, size.Width, size.Height));
}
public override void EndPage()
{
var picture = PictureRecorder?.EndRecording();
if (picture != null && CurrentPageSize.HasValue)
Pictures.Add(new PreviewerPicture(picture, CurrentPageSize.Value));
PictureRecorder?.Dispose();
PictureRecorder = null;
}
public override void EndDocument() { }
}
}
+155
View File
@@ -0,0 +1,155 @@
using System;
using HarfBuzzSharp;
using QuestPDF.Infrastructure;
using SkiaSharp;
using Buffer = HarfBuzzSharp.Buffer;
namespace QuestPDF.Drawing
{
internal class TextShaper
{
public const int FontShapingScale = 512;
private Font Font { get; }
private SKPaint Paint { get; }
public TextShaper(TextStyle style)
{
Font = style.ToShaperFont();
Paint = style.ToPaint();
}
public TextShapingResult Shape(string text)
{
var buffer = new Buffer();
PopulateBufferWithText(buffer, text);
buffer.GuessSegmentProperties();
Font.Shape(buffer);
var length = buffer.Length;
var glyphInfos = buffer.GlyphInfos;
var glyphPositions = buffer.GlyphPositions;
var scaleY = Paint.TextSize / FontShapingScale;
var scaleX = scaleY * Paint.TextScaleX;
var xOffset = 0f;
var yOffset = 0f;
var glyphs = new ShapedGlyph[length];
for (var i = 0; i < length; i++)
{
glyphs[i] = new ShapedGlyph
{
Codepoint = (ushort)glyphInfos[i].Codepoint,
Position = new SKPoint(xOffset + glyphPositions[i].XOffset * scaleX, yOffset - glyphPositions[i].YOffset * scaleY),
Width = glyphPositions[i].XAdvance * scaleX
};
xOffset += glyphPositions[i].XAdvance * scaleX;
yOffset += glyphPositions[i].YAdvance * scaleY;
}
return new TextShapingResult(glyphs);
}
void PopulateBufferWithText(Buffer buffer, string text)
{
var encoding = Paint.TextEncoding;
if (encoding == SKTextEncoding.Utf8)
buffer.AddUtf8(text);
else if (encoding == SKTextEncoding.Utf16)
buffer.AddUtf16(text);
else if (encoding == SKTextEncoding.Utf32)
buffer.AddUtf32(text);
else
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
}
}
internal struct ShapedGlyph
{
public ushort Codepoint;
public SKPoint Position;
public float Width;
}
internal struct DrawTextCommand
{
public SKTextBlob SkTextBlob;
public float TextOffsetX;
}
internal class TextShapingResult
{
public ShapedGlyph[] Glyphs { get; }
public TextShapingResult(ShapedGlyph[] glyphs)
{
Glyphs = glyphs;
}
public int BreakText(int startIndex, float maxWidth)
{
var index = startIndex;
maxWidth += Glyphs[startIndex].Position.X;
while (index < Glyphs.Length)
{
var glyph = Glyphs[index];
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
break;
index++;
}
return index - 1;
}
public float MeasureWidth(int startIndex, int endIndex)
{
if (Glyphs.Length == 0)
return 0;
var start = Glyphs[startIndex];
var end = Glyphs[endIndex];
return end.Position.X - start.Position.X + end.Width;
}
public DrawTextCommand? PositionText(int startIndex, int endIndex, TextStyle textStyle)
{
if (Glyphs.Length == 0)
return null;
using var skTextBlobBuilder = new SKTextBlobBuilder();
var positionedRunBuffer = skTextBlobBuilder.AllocatePositionedRun(textStyle.FoFont(), endIndex - startIndex + 1);
var glyphSpan = positionedRunBuffer.GetGlyphSpan();
var positionSpan = positionedRunBuffer.GetPositionSpan();
for (var sourceIndex = startIndex; sourceIndex <= endIndex; sourceIndex++)
{
var runIndex = sourceIndex - startIndex;
glyphSpan[runIndex] = Glyphs[sourceIndex].Codepoint;
positionSpan[runIndex] = Glyphs[sourceIndex].Position;
}
return new DrawTextCommand
{
SkTextBlob = skTextBlobBuilder.Build(),
TextOffsetX = -Glyphs[startIndex].Position.X
};
}
}
}
+16 -2
View File
@@ -1,4 +1,6 @@
using System.IO;
using System;
using System.IO;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Helpers;
using SkiaSharp;
@@ -7,9 +9,21 @@ namespace QuestPDF.Drawing
internal class XpsCanvas : SkiaDocumentCanvasBase
{
public XpsCanvas(Stream stream, DocumentMetadata documentMetadata)
: base(SKDocument.CreateXps(stream, documentMetadata.RasterDpi))
: base(CreateXps(stream, documentMetadata))
{
}
private static SKDocument CreateXps(Stream stream, DocumentMetadata documentMetadata)
{
try
{
return SKDocument.CreateXps(stream, documentMetadata.RasterDpi);
}
catch (TypeInitializationException exception)
{
throw new InitializationException("XPS", exception);
}
}
}
}
+3
View File
@@ -41,6 +41,9 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace)
{
if (!Items.Any())
return SpacePlan.FullRender(Size.Zero);
var renderingCommands = PlanLayout(availableSpace);
if (!renderingCommands.Any())

Some files were not shown because too many files have changed in this diff Show More