diff --git a/.editorconfig b/.editorconfig
index 9f785dcd..550e3798 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -78,6 +78,7 @@ dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
dotnet_style_readonly_field = true:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+dotnet_style_allow_multiple_blank_lines_experimental = true:silent
[*.cs]
csharp_indent_labels = one_less_than_current
@@ -111,4 +112,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_prefer_static_local_function = true:suggestion
-csharp_style_prefer_readonly_struct = true:suggestion
\ No newline at end of file
+csharp_style_prefer_readonly_struct = true:suggestion
+csharp_prefer_system_threading_lock = true:suggestion
+csharp_prefer_static_anonymous_function = true:suggestion
+csharp_style_prefer_readonly_struct_member = true:suggestion
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e136e4a4..82a21a82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,7 @@
assets/
TestResults/
localtests/
-.nuget/
.build/
-.testPublish/
*.sln.ide/
_ReSharper.*/
.idea/
@@ -15,41 +13,43 @@ PublishProfiles/
.vs/
.vscode/
.idea/
-bower_components/
-node_modules/
+# bower_components/
+# node_modules/
+zip/
debugSettings.json
project.lock.json
*.log
*.user
-*.suo
+#*.suo
*.cache
-*.docstates
+#*.docstates
+*.zip
_ReSharper.*
nuget.exe
-*net45.csproj
-*net451.csproj
-*k10.csproj
-*.psess
+#*net45.csproj
+#*net451.csproj
+#*k10.csproj
+#*.psess
*.vsp
*.pidb
*.userprefs
*DS_Store
-*.ncrunchsolution
-nCrunchTemp*.*
-*.*sdf
-*.ipch
-.settings
+#*.ncrunchsolution
+#nCrunchTemp*.*
+#*.*sdf
+#*.ipch
+#.settings
*.sln.ide
node_modules
# *launchSettings.json
*.orig
*.nuget.props
*.nuget.targets
-/localfeed
-oldstate.json
-InternalAgentState.json
+#/localfeed
+#oldstate.json
+#InternalAgentState.json
/nuget.config
*.log
-*tempfile.*
+#*tempfile.*
launchsettings.json
testEnvironments.json
diff --git a/.globalconfig b/.globalconfig
new file mode 100644
index 00000000..e0f231a2
--- /dev/null
+++ b/.globalconfig
@@ -0,0 +1,3 @@
+is_global = true
+
+dotnet_diagnostic.IDE0290.severity = suggestion
diff --git a/LICENSE b/LICENSE
index 70d0bf3c..03b0a374 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2001-2025 empira Software GmbH, Troisdorf (Cologne Area), Germany
+Copyright (c) 2001-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany
http://docs.pdfsharp.net
diff --git a/PdfSharp.sln b/PdfSharp.sln
index 3c82ce5b..b2b46225 100644
--- a/PdfSharp.sln
+++ b/PdfSharp.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31912.275
+# Visual Studio Version 18
+VisualStudioVersion = 18.0.11205.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{18632E7D-54DF-4933-A8DF-A7ECE8666E9B}"
ProjectSection(SolutionItems) = preProject
@@ -9,7 +9,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{18632E7D-54D
src\Directory.Build.targets = src\Directory.Build.targets
src\Directory.Packages.props = src\Directory.Packages.props
src\dotnet-test.Build.props = src\dotnet-test.Build.props
+ src\Local.Build.props = src\Local.Build.props
+ src\Local.Build.props---template = src\Local.Build.props---template
src\nuget.config = src\nuget.config
+ src\PdfSharpBuildConfig.props = src\PdfSharpBuildConfig.props
+ src\SemVersion.props = src\SemVersion.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PDFsharp", "PDFsharp", "{5912CE0D-0DCE-479F-944A-0DFA4CE95A88}"
@@ -30,11 +34,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "foundation", "foundation",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8770F3E1-3FEE-4611-89D4-AF80DFB3B3EA}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "projects", "projects", "{FD3FEC8C-C8C8-47F1-AF08-F77EC937ACFE}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{EF9C1FA7-24B3-438A-BD21-400C1500E164}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{71BD4587-A8B0-4653-A3B2-93578F71ABB7}"
+ ProjectSection(SolutionItems) = preProject
+ src\foundation\src\PDFsharp\tests\Directory.Build.props = src\foundation\src\PDFsharp\tests\Directory.Build.props
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Tests", "src\foundation\src\PDFsharp\tests\PdfSharp.Tests\PdfSharp.Tests.csproj", "{0B1C5283-63AC-4B2C-B0B2-D2FFD4BB3BD2}"
EndProject
@@ -104,7 +109,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{31FE507C-F342-4DA4-9BE5-B3D2ED93E9CC}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
- gitversion-6.x.yml = gitversion-6.x.yml
gitversion.yml = gitversion.yml
LICENSE = LICENSE
README.md = README.md
@@ -118,8 +122,14 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Quality", "src\foundation\src\shared\src\PdfSharp.Quality\PdfSharp.Quality.csproj", "{B0BEB589-A3A7-4412-B045-09621FB1DE25}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Snippets", "src\foundation\src\shared\src\PdfSharp.Snippets\PdfSharp.Snippets.csproj", "{E4D95A70-171A-4027-BFCE-AB2B1907C6C1}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.WPFonts", "src\foundation\src\shared\src\PdfSharp.WPFonts\PdfSharp.WPFonts.csproj", "{034B233F-0745-476A-A3DB-8DDF8FA0EF9C}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}
+ EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "features", "features", "{F6F7E411-5CD0-4507-BF59-70004EDD09DA}"
EndProject
@@ -128,6 +138,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MigraDoc.Features", "src\foundation\src\MigraDoc\features\MigraDoc.Features\MigraDoc.Features.csproj", "{05E1F63A-EC20-4280-86A2-7971691DA964}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Features", "src\foundation\src\PDFsharp\features\PdfSharp.Features\PdfSharp.Features.csproj", "{E42F855D-9A4F-45BE-9950-F20A94169568}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp-wpf", "src\foundation\src\PDFsharp\src\PdfSharp-wpf\PdfSharp-wpf.csproj", "{5BB33F6F-0151-4D8F-8530-249A9C3B9B23}"
EndProject
@@ -140,12 +153,24 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Quality-gdi", "src\foundation\src\shared\src\PdfSharp.Quality-gdi\PdfSharp.Quality-gdi.csproj", "{B0EE3DF4-34E1-4C69-836E-B8911192D3FE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Snippets-wpf", "src\foundation\src\shared\src\PdfSharp.Snippets-wpf\PdfSharp.Snippets-wpf.csproj", "{8F4002CD-E002-4C61-9E7A-0D78875C4767}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Snippets-gdi", "src\foundation\src\shared\src\PdfSharp.Snippets-gdi\PdfSharp.Snippets-gdi.csproj", "{76959A2A-8113-430B-8272-DD37F17F9305}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDFsharp.Features-wpf", "src\foundation\src\PDFsharp\features\PDFsharp.Features-wpf\PDFsharp.Features-wpf.csproj", "{E19EF259-4C5F-4967-867C-EF3936B264C4}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDFsharp.Features-gdi", "src\foundation\src\PDFsharp\features\PDFsharp.Features-gdi\PDFsharp.Features-gdi.csproj", "{ECCE20D4-000F-4D15-87B5-0335C7B3163A}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Charting-wpf", "src\foundation\src\PDFsharp\src\PdfSharp.Charting-wpf\PdfSharp.Charting-wpf.csproj", "{2E3778BB-E153-47C6-AD2C-E69F0B242578}"
EndProject
@@ -236,8 +261,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Fonts", "src\found
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Fonts.TestApp", "src\foundation\src\shared\testapps\PdfSharp.Fonts.TestApp\PdfSharp.Fonts.TestApp.csproj", "{2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Fonts.Test", "src\foundation\src\shared\tests\PdfSharp.Fonts.Test\PdfSharp.Fonts.Test.csproj", "{D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Testing", "src\foundation\src\shared\src\PdfSharp.Testing\PdfSharp.Testing.csproj", "{D17FA884-98B9-41E2-AB27-2603F79000F5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dev", "dev", "{BA039CC9-132E-4103-BB47-87C3E3398F82}"
@@ -251,6 +274,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dev", "dev", "{BA039CC9-132
dev\run-tests.ps1 = dev\run-tests.ps1
dev\update-local-nuget-packages-debug.ps1 = dev\update-local-nuget-packages-debug.ps1
dev\update-local-nuget-packages-release.ps1 = dev\update-local-nuget-packages-release.ps1
+ dev\zip-PDFsharp.ps1 = dev\zip-PDFsharp.ps1
dev\_update-local-nuget-packages.ps1 = dev\_update-local-nuget-packages.ps1
EndProjectSection
EndProject
@@ -270,10 +294,30 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.BarCodes-wpf", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.BarCodes", "src\foundation\src\PDFsharp\src\PdfSharp.BarCodes\PdfSharp.BarCodes.csproj", "{F4AB506C-AD13-4383-9AF9-48D74085ECC1}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs-dummy", "docs\docs-dummy.csproj", "{76B94284-402D-4951-8DA4-7FFAF15E6C95}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Cryptography", "src\foundation\src\PDFsharp\src\PdfSharp.Cryptography\PdfSharp.Cryptography.csproj", "{769ED050-15AF-4EB5-A89F-D7123EE5AA95}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{0E0ABA4E-19EE-4D84-8BD7-734AC1BA5AE0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A0D471A6-12D0-4F4E-96BE-32B461547028}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Extensions", "src\foundation\src\extensions\src\PdfSharp.Extensions\PdfSharp.Extensions.csproj", "{2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.BuildConfig", "src\foundation\src\shared\src\PdfSharp.BuildConfig\PdfSharp.BuildConfig.csproj", "{7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Quality.Ghostscript", "src\foundation\src\shared\src\PdfSharp.Quality.Ghostscript\PdfSharp.Quality.Ghostscript.csproj", "{59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Imaging", "src\foundation\src\shared\src\PdfSharp.Imaging\PdfSharp.Imaging.csproj", "{22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.OpenType", "src\foundation\src\shared\src\PdfSharp.OpenType\PdfSharp.OpenType.csproj", "{08CB46DA-52D2-AB40-1E42-A8FC799565F8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PDFsharpGraphics", "PDFsharpGraphics", "{5854C6A1-5FDB-497C-A5DB-74AF372B58BF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Fonts.Test", "src\foundation\src\shared\tests\PdfSharp.Fonts.Tests\PdfSharp.Fonts.Test.csproj", "{2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.OpenType.Test", "src\foundation\src\shared\tests\PdfSharp.OpenType.Tests\PdfSharp.OpenType.Test.csproj", "{A7A57937-E309-9E58-A3B4-6B268E6933E7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs-public", "docs\docs-public.csproj", "{B75C5BBF-4110-E576-C755-66CBF94A4CD9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -524,10 +568,6 @@ Global
{2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776}.Release|Any CPU.Build.0 = Release|Any CPU
- {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}.Release|Any CPU.Build.0 = Release|Any CPU
{D17FA884-98B9-41E2-AB27-2603F79000F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D17FA884-98B9-41E2-AB27-2603F79000F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D17FA884-98B9-41E2-AB27-2603F79000F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -560,14 +600,42 @@ Global
{F4AB506C-AD13-4383-9AF9-48D74085ECC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4AB506C-AD13-4383-9AF9-48D74085ECC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4AB506C-AD13-4383-9AF9-48D74085ECC1}.Release|Any CPU.Build.0 = Release|Any CPU
- {76B94284-402D-4951-8DA4-7FFAF15E6C95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {76B94284-402D-4951-8DA4-7FFAF15E6C95}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {76B94284-402D-4951-8DA4-7FFAF15E6C95}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {76B94284-402D-4951-8DA4-7FFAF15E6C95}.Release|Any CPU.Build.0 = Release|Any CPU
{769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Debug|Any CPU.Build.0 = Debug|Any CPU
{769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}.Release|Any CPU.Build.0 = Release|Any CPU
+ {08CB46DA-52D2-AB40-1E42-A8FC799565F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {08CB46DA-52D2-AB40-1E42-A8FC799565F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {08CB46DA-52D2-AB40-1E42-A8FC799565F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {08CB46DA-52D2-AB40-1E42-A8FC799565F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7A57937-E309-9E58-A3B4-6B268E6933E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7A57937-E309-9E58-A3B4-6B268E6933E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7A57937-E309-9E58-A3B4-6B268E6933E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7A57937-E309-9E58-A3B4-6B268E6933E7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B75C5BBF-4110-E576-C755-66CBF94A4CD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B75C5BBF-4110-E576-C755-66CBF94A4CD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B75C5BBF-4110-E576-C755-66CBF94A4CD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B75C5BBF-4110-E576-C755-66CBF94A4CD9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -579,7 +647,6 @@ Global
{E97F1455-620B-4BF2-B839-08F7AE2FD772} = {E1933982-1D12-497F-B465-67D5E5C74CA1}
{0EDABDD2-05D5-43D0-A635-645DD8127201} = {18632E7D-54DF-4933-A8DF-A7ECE8666E9B}
{8770F3E1-3FEE-4611-89D4-AF80DFB3B3EA} = {0EDABDD2-05D5-43D0-A635-645DD8127201}
- {FD3FEC8C-C8C8-47F1-AF08-F77EC937ACFE} = {18632E7D-54DF-4933-A8DF-A7ECE8666E9B}
{EF9C1FA7-24B3-438A-BD21-400C1500E164} = {18632E7D-54DF-4933-A8DF-A7ECE8666E9B}
{71BD4587-A8B0-4653-A3B2-93578F71ABB7} = {5912CE0D-0DCE-479F-944A-0DFA4CE95A88}
{0B1C5283-63AC-4B2C-B0B2-D2FFD4BB3BD2} = {71BD4587-A8B0-4653-A3B2-93578F71ABB7}
@@ -662,7 +729,6 @@ Global
{284F0281-F283-4EC3-BD60-DD0CD5A5442F} = {CC13B431-6963-480F-8C21-1F78A220A399}
{9EC27C83-FCD6-44B8-9BC6-9F270DD58C8E} = {DE458BB2-5942-4588-A74A-AE9CA86F284C}
{2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776} = {9A382224-6874-4ED3-8DF6-1DD3466A02CE}
- {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14} = {A1AE894D-9061-44F0-8268-328AC41F403A}
{D17FA884-98B9-41E2-AB27-2603F79000F5} = {DE458BB2-5942-4588-A74A-AE9CA86F284C}
{BA039CC9-132E-4103-BB47-87C3E3398F82} = {31FE507C-F342-4DA4-9BE5-B3D2ED93E9CC}
{C7CCB288-8372-41AD-9F65-6224CE0D6358} = {DE458BB2-5942-4588-A74A-AE9CA86F284C}
@@ -673,8 +739,18 @@ Global
{E6E3A838-95C6-462D-84E4-FAFF69DA95C0} = {7C753636-7947-46E0-95E0-135EAA7BFEB3}
{4CBBAD35-0D01-4BDD-8BDF-B6022497938E} = {7C753636-7947-46E0-95E0-135EAA7BFEB3}
{F4AB506C-AD13-4383-9AF9-48D74085ECC1} = {7C753636-7947-46E0-95E0-135EAA7BFEB3}
- {76B94284-402D-4951-8DA4-7FFAF15E6C95} = {76BA9372-65AE-479C-AEF7-D50E6B486CEF}
{769ED050-15AF-4EB5-A89F-D7123EE5AA95} = {7C753636-7947-46E0-95E0-135EAA7BFEB3}
+ {0E0ABA4E-19EE-4D84-8BD7-734AC1BA5AE0} = {8770F3E1-3FEE-4611-89D4-AF80DFB3B3EA}
+ {A0D471A6-12D0-4F4E-96BE-32B461547028} = {0E0ABA4E-19EE-4D84-8BD7-734AC1BA5AE0}
+ {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D} = {A0D471A6-12D0-4F4E-96BE-32B461547028}
+ {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {DE458BB2-5942-4588-A74A-AE9CA86F284C}
+ {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD} = {DE458BB2-5942-4588-A74A-AE9CA86F284C}
+ {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958} = {DE458BB2-5942-4588-A74A-AE9CA86F284C}
+ {08CB46DA-52D2-AB40-1E42-A8FC799565F8} = {DE458BB2-5942-4588-A74A-AE9CA86F284C}
+ {5854C6A1-5FDB-497C-A5DB-74AF372B58BF} = {8770F3E1-3FEE-4611-89D4-AF80DFB3B3EA}
+ {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D} = {A1AE894D-9061-44F0-8268-328AC41F403A}
+ {A7A57937-E309-9E58-A3B4-6B268E6933E7} = {A1AE894D-9061-44F0-8268-328AC41F403A}
+ {B75C5BBF-4110-E576-C755-66CBF94A4CD9} = {76BA9372-65AE-479C-AEF7-D50E6B486CEF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D5FF5562-3C79-434B-B951-B84542D01625}
diff --git a/PdfSharp.sln.DotSettings b/PdfSharp.sln.DotSettings
index 1415191e..d94e93f8 100644
--- a/PdfSharp.sln.DotSettings
+++ b/PdfSharp.sln.DotSettings
@@ -6,10 +6,12 @@
BSD
CF
CR
+ DA
IO
IV
MD
OSX
+ OT
SASL
TSA
URI
@@ -23,6 +25,7 @@
Edit
True
ID
+ True
True
True
True
@@ -32,6 +35,7 @@
True
True
True
+ True
True
True
True
@@ -40,6 +44,7 @@
True
True
True
+ True
True
True
True
@@ -67,6 +72,7 @@
True
True
True
+ True
True
True
True
diff --git a/README.md b/README.md
index defc0f4c..703b5e24 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
-# PDFsharp & MigraDoc 6
+# PDFsharp & MigraDoc 7
-Version **6.2.0**
-Published **2025-05-19**
+Version **7.0.0 Preview 1**
+Published **2026-03-24**
-This is a final version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 6 with updates for C# 12 and .NET 6.
+This is a preview version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 7 with updates for C# 12 and .NET 8, .NET 9, and .NET 10.
-PDFsharp: Copyright (c) 2005-2025 empira Software GmbH, Troisdorf (Cologne Area), Germany
-MigraDoc: Copyright (c) 2001-2025 empira Software GmbH, Troisdorf (Cologne Area), Germany
+PDFsharp: Copyright (c) 2005-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany
+MigraDoc: Copyright (c) 2001-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany
Published Open Source under the [MIT License](https://docs.pdfsharp.net/LICENSE.html)
For more information see [docs.pdfsharp.net](https://docs.pdfsharp.net/)
@@ -15,7 +15,7 @@ For more information see [docs.pdfsharp.net](https://docs.pdfsharp.net/)
Project documentation can be found on our DOCS site: .
-Note: PowerShell 7 is required to execute the PowerShell scripts that come with PDFsharp.
+Note that PowerShell 7 is required to execute the PowerShell scripts that come with PDFsharp.
### Download assets first
@@ -34,9 +34,10 @@ Execute
`dotnet build` should build the solution without any warnings or errors.
* You need the latest .NET SDK version installed
-* Please note that you need a git repository with at least one commit in order to build the PDFsharp solution.
+* ~~Please note that you need a git repository with at least one commit in order to build the PDFsharp solution.
Without a git repository with at least one commit, you will get an error message from `GitVersion.MsBuild` while building the solution.
- You can set a tag to define a valid version, e.g.: `git tag v6.2.0` to make it build with a specific version number. Without tag, version 0.1.0 will be used.
+ You can set a tag to define a valid version, e.g.: `git tag v6.2.0` to make it build with a specific version number. Without tag, version 0.1.0 will be used.~~
+ (Is now done automatically by BuildConfig)
### Central package management
diff --git a/dev/GetAllPDFs.ps1 b/dev/GetAllPDFs.ps1
new file mode 100644
index 00000000..3029108f
--- /dev/null
+++ b/dev/GetAllPDFs.ps1
@@ -0,0 +1,41 @@
+# Chat/GPT creates this script for me. That is why comments are German.
+# param(
+# [Parameter(Mandatory = $true)]
+# [string]$SourcePath,
+
+# [Parameter(Mandatory = $true)]
+# [string]$DestinationPath
+# )
+
+TODO verschieben
+
+$SourcePath = "D:\repos\emp\PDFsharp.iText\itext.tests"
+$DestinationPath = "D:\repos\emp\PDFsharp\assets\private\test-files"
+
+# Zielverzeichnis erstellen, falls es nicht existiert
+if (-not (Test-Path -Path $DestinationPath)) {
+ New-Item -ItemType Directory -Path $DestinationPath | Out-Null
+}
+
+# Alle PDF-Dateien rekursiv suchen
+$files = Get-ChildItem -Path $SourcePath -Recurse -Filter *.pdf
+
+foreach ($file in $files) {
+ # Dateiname ermitteln
+ $destFile = Join-Path $DestinationPath $file.Name
+
+ # Falls Datei schon existiert → eindeutigen Namen erzeugen
+ if (Test-Path $destFile) {
+ $baseName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name)
+ $extension = $file.Extension
+ $counter = 1
+ do {
+ $newName = "$baseName`_$counter$extension"
+ $destFile = Join-Path $DestinationPath $newName
+ $counter++
+ } while (Test-Path $destFile)
+ }
+
+ # Datei kopieren
+ Copy-Item -Path $file.FullName -Destination $destFile
+}
diff --git a/dev/README.md b/dev/README.md
index 370831f3..af9f5f16 100644
--- a/dev/README.md
+++ b/dev/README.md
@@ -15,3 +15,9 @@ Builds NuGet packages for release on NuGet.org.
## run-tests
Run unit tests on Windows and WSL2.
+
+## set-en.ps1
+Sets UI language for dotnet build etc. to en-US.
+
+## zip-PDFsharp.ps1
+Creates a PDFsharp.zip file.
diff --git a/dev/_update-local-nuget-packages.ps1 b/dev/_update-local-nuget-packages.ps1
index 943e2410..7121d72b 100644
--- a/dev/_update-local-nuget-packages.ps1
+++ b/dev/_update-local-nuget-packages.ps1
@@ -22,21 +22,70 @@ $b = "`e[94m"
$r = "`e[0m"
# Set working directory to solution root.
-Push-Location $PSScriptRoot
-Push-Location .\..
+Push-Location $PSScriptRoot\..
-Write-Host "Copy all `e[93m$($config.ToUpperInvariant())$r NuGet packages to $b$nugetLocal$r."
-New-Item -Path $nugetLocal -ItemType directory -Force | Out-Null
-$packages = @()
-Get-ChildItem -Path . -Filter *.nupkg -Recurse -ErrorAction SilentlyContinue -Force | ForEach-Object {
- if ($_.FullName -match "bin\\$config|bin\/$config") {
- Copy-Item $_.FullName -Destination ("$nugetLocal\" + $_.Name)
- $packages += $_.Name
+# Machine local NuGet packages
+# Do not copy packages into user profile folder NugetFolder
+# if .nuget-local does not exist.
+if (Test-Path -Path $nugetLocal) {
+ # User has a local NuGet directory.
+} else {
+ $nugetLocal = ""
+}
+
+# Project local NuGet packages (if PDFsharp is used as submodule)
+# Do not copy packages into .nuget directory if PDFsharp is not used
+# as a submodule and therefore .nuget does not exist in project directory.
+$nugetProject = "..\..\.nuget" # because of this: YOUR-PROJECT/modules/PDFsharp
+if (Test-Path -Path $nugetProject) {
+ # PDFsharp is a submodule and the outer project has a .nuget directory.
+ $nugetProject = $nugetProject
+} else {
+ $nugetProject = ""
+}
+
+$hasNugetLocal = $nugetLocal.Length -gt 0
+$hasNugetProject = $nugetProject.Length -gt 0
+
+if ($hasNugetLocal -or $hasNugetProject)
+{
+ if ($hasNugetLocal -and $hasNugetProject)
+ {
+ $targetFolders = "$b$nugetLocal$r and $b$nugetProject$r"
+ }
+ elseif ($hasNugetLocal)
+ {
+ $targetFolders = "$b$nugetLocal$r"
}
+ elseif ($hasNugetProject)
+ {
+ $targetFolders = "$b$nugetProject$r"
+ }
+
+ Write-Output "Copy all `e[93m$($config.ToUpperInvariant())$r NuGet packages to $targetFolders."
+ # Do not force creation of directory anymore.
+ # New-Item -Path $nugetLocal -ItemType directory -Force | Out-Null
+ $packages = @()
+ Get-ChildItem -Path . -Filter *.nupkg -Recurse -ErrorAction SilentlyContinue -Force | ForEach-Object {
+ if ($_.FullName -match "bin\\$config|bin\/$config") {
+ if ($nugetLocal.Length -gt 0) {
+ Copy-Item $_.FullName -Destination ("$nugetLocal\" + $_.Name)
+ }
+ if ($nugetProject.Length -gt 0) {
+ Copy-Item $_.FullName -Destination ("$nugetProject\" + $_.Name)
+ }
+ $packages += $_.Name
+ }
+ }
+ Write-Output " $b$($packages.Count)$r packages copied."
}
-Write-Host " $b$($packages.Count)$r packages copied."
+else
+{
+ Write-Output "No `e[93m$($config.ToUpperInvariant())$r NuGet packages are copied as there is no .nuget-local and no main module .nuget folder."
+}
+
-Write-Host "Delete all existing old package folders in $b$nuget$r folder."
+Write-Output "Delete all existing old package folders in $b$nuget$r folder."
$count = 0
$versions = @()
$packages | ForEach-Object {
@@ -63,10 +112,11 @@ $packages | ForEach-Object {
}
}
}
-Write-Host " $b$count$r package $($deleteAllPackageVersions ? '' : 'version ')folders deleted in $b$nuget$r."
+Write-Output " $b$count$r package $($deleteAllPackageVersions ? '' : 'version ')folders deleted in $b$nuget$r."
if ($packages.Count -gt 0) {
- Write-Host " New version number(s) : $b$Versions$r"
+ # In case you build several times and do not delete the artifacts
+ # you can have more than one set of NuGet packages
+ Write-Output " The new version number(s) are: $b$Versions$r"
}
Pop-Location
-Pop-Location
diff --git a/dev/build-local-nuget-packages-release.ps1 b/dev/build-local-nuget-packages-release.ps1
index 14be2328..7992933c 100644
--- a/dev/build-local-nuget-packages-release.ps1
+++ b/dev/build-local-nuget-packages-release.ps1
@@ -1,4 +1,16 @@
-# Updates local nuget packages.
+<#
+.SYNOPSIS
+ Build PDFsharp with config release and updates the local nuget packages.
+
+.DESCRIPTION
+ The script deletes all artifacts and builds a PDFsharp release version.
+ It first builds the PdfSharp.BuildConfig.csproj to ensure that SemVersion.props and
+ PDFsharpBuildConfig.props are up-to-date before the build process starts.
+ The created packages are copied to two locations.
+ The first is the .nuget-local directory in your user profile directory.
+ The second is the .nuget directory in the root folder of your project in case
+ you used PDFsharp as a submodule.
+#>
#Requires -Version 7
#Requires -PSEdition Core
@@ -9,31 +21,35 @@ param (
Push-Location $PSScriptRoot
-try {
- Write-Host "Delete bin and obj " $deleteBinAndObj
+try {
+ # Write-Output "Delete bin and obj " $deleteBinAndObj
if ($deleteBinAndObj) {
- Write-Host "Deleting BIN and OBJ"
- .\del-bin-and-obj.ps1
- Write-Host "Done deleting bin and obj"
+ Write-Output "Deleting ‘/bin’ and ‘/obj’..."
+ .\del-bin-and-obj.ps1 | Out-Null
+ Write-Output "Done."
}
Push-Location ..
try {
- Write-Host "Invoking ‘dotnet build’"
- dotnet build -c release
+ Write-Output "Invoking ‘dotnet build’ for ‘PdfSharp.BuildConfig.csproj’"
+ # Generate semver infos and PDFsharp build configuration first.
+ dotnet build .\src\foundation\src\shared\src\PdfSharp.BuildConfig\PdfSharp.BuildConfig.csproj
+ Write-Output "Invoking ‘dotnet build’ for PDFsharp solution"
+ dotnet build --configuration release
$build = $LASTEXITCODE
- Write-Host "‘dotnet build’ has finished"
+ Write-Output "‘dotnet build’ has finished"
}
finally {
Pop-Location
}
if ($build -gt 0) {
- Write-Host "‘dotnet build’ failed with code " $build
+ Write-Error "‘dotnet build’ failed with code " $build
throw "‘dotnet build’ failed with code " + $build
}
- .\update-local-nuget-packages-release.ps1
+ Write-Output "Invoking ‘update-local-nuget-packages-release.ps1’"
+ .\update-local-nuget-packages-release.ps1
}
finally {
Pop-Location
diff --git a/dev/del-bin-and-obj.ps1 b/dev/del-bin-and-obj.ps1
index 335f017d..fc9c7ec6 100644
--- a/dev/del-bin-and-obj.ps1
+++ b/dev/del-bin-and-obj.ps1
@@ -4,5 +4,14 @@
#Requires -PSEdition Core
Push-Location $PSScriptRoot\..\
-Get-ChildItem .\ -include bin,obj -Recurse | ForEach-Object ($_) { remove-item $_.fullname -Force -Recurse }
+if (Test-Path .\src\SemVersion.props) {
+ Remove-Item .\src\SemVersion.props
+ & .\src\foundation\src\shared\src\PdfSharp.BuildConfig\CreateBuildConfiguration.ps1
+}
+Write-Output "Deleting..."
+Get-ChildItem .\ -include bin,obj -Recurse |
+ForEach-Object ($_) {
+ Remove-Item $_.fullname -Force -Recurse | Out-Null
+}
+Write-Output "Done."
Pop-Location
diff --git a/dev/download-assets.ps1 b/dev/download-assets.ps1
index 6f9db34b..f9d5dd8b 100644
--- a/dev/download-assets.ps1
+++ b/dev/download-assets.ps1
@@ -23,9 +23,9 @@ $source = "https://assets.pdfsharp.net/"
$destination = "$PSScriptRoot/../assets/"
if (test-path -PathType container $destination) {
- Remove-Item -LiteralPath $destination -Force -Recurse
+ Remove-Item -LiteralPath $destination -Force -Recurse | Out-Null
}
-New-Item -ItemType Directory -Path $destination
+New-Item -ItemType Directory -Path $destination | Out-Null
# Download assets version.
$url = $source + ".assets-version"
@@ -38,7 +38,7 @@ foreach ($asset in $assetList) {
$dest = $destination + $asset
$folder = [IO.Path]::GetDirectoryName($dest)
- New-Item -ItemType Directory -Path $folder -Force
+ New-Item -ItemType Directory -Path $folder -Force | Out-Null
Invoke-WebRequest $url -OutFile $dest
@@ -56,8 +56,8 @@ foreach ($asset in $assetList) {
$source = "https://assets.pdfsharp.net/"
$destination = "$PSScriptRoot/../assets/fonts/Noto/Noto_Sans/static/"
-New-Item -ItemType Directory -Path $destination
-New-Item -ItemType Directory -Path "$destination/temp/"
+New-Item -ItemType Directory -Path $destination | Out-Null
+New-Item -ItemType Directory -Path "$destination/temp/" | Out-Null
$url = $source + "fonts/Noto/Noto_Sans.zip"
$dest = $destination
diff --git a/dev/init-pdfsharp-repository.ps1 b/dev/init-pdfsharp-repository.ps1
new file mode 100644
index 00000000..c950826b
--- /dev/null
+++ b/dev/init-pdfsharp-repository.ps1
@@ -0,0 +1,21 @@
+# Initialize PDFsharp repository after a fresh git clone.
+
+#Requires -Version 7
+#Requires -PSEdition Core
+
+Push-Location $PSScriptRoot/..
+
+# Download latest assets. TODO
+# ./dev/download-assets.ps1
+ ./dev/del-bin-and-obj.ps1
+
+# Create correct git version information by producing
+# SemVersion.props and SemVersionInformation-generated.cs.
+dotnet build .\src\foundation\src\shared\src\PdfSharp.GitVersion\PdfSharp.GitVersion.csproj
+
+# Build the first time DEBUG and RELEASE
+# Compile for testing
+dotnet build
+dotnet build --configuration release
+
+Pop-Location
diff --git a/dev/run-tests.ps1 b/dev/run-tests.ps1
index 44caf47a..5ecbe06e 100644
--- a/dev/run-tests.ps1
+++ b/dev/run-tests.ps1
@@ -112,9 +112,9 @@ function InitializeScript()
exit
}
- Write-Host
- Write-Host "Started run-tests for solution `"$script:Solution`"."
- Write-Host
+ Write-Output ""
+ Write-Output "Started run-tests for solution `"$script:Solution`"."
+ Write-Output ""
$script:TimeStart = Get-Date
$script:ConsoleWidth = $Host.UI.RawUI.WindowSize.Width
@@ -167,43 +167,43 @@ function InitializeScript()
}
# Output current system information.
- Write-Host
- Write-Host "Started script in $script:SystemNameHost."
+ Write-Output ""
+ Write-Output "Started script in $script:SystemNameHost."
if ($script:RunOnHostedWsl)
{
- Write-Host "Tests will be run on $script:SystemNameHost host and hosted $script:SystemNameCurrentLinux."
+ Write-Output "Tests will be run on $script:SystemNameHost host and hosted $script:SystemNameCurrentLinux."
}
else
{
- Write-Host "Tests will be run on $script:SystemNameHost host only."
+ Write-Output "Tests will be run on $script:SystemNameHost host only."
}
- Write-Host
+ Write-Output ""
if ($script:Net6)
{
- Write-Host "NET6 Tests will be run instead of NET8."
- Write-Host
+ Write-Output "NET6 Tests will be run instead of NET8."
+ Write-Output ""
}
if ($script:SkipBuild)
{
- Write-Host "Building solution in $script:Config build will be skipped."
- Write-Host
+ Write-Output "Building solution in $script:Config build will be skipped."
+ Write-Output ""
}
if ($script:RunAllTests)
{
$env:PDFsharpTests = "runalltests"
$script:EnvironmentForHostedWsl = "PDFsharpTests=runalltests"
- Write-Host "Running all tests of solution."
+ Write-Output "Running all tests of solution."
}
else
{
$env:PDFsharpTests = $null
$script:EnvironmentForHostedWsl = "PDFsharpTests=$null"
- Write-Host "Skipping slow tests of solution."
+ Write-Output "Skipping slow tests of solution."
}
- Write-Host
+ Write-Output ""
CheckNetRuntimes
}
@@ -263,10 +263,10 @@ function BuildSolution()
return
}
- Write-Host
- Write-Host "Building solution in $script:Config build"
- Write-Host ==================================================
- Write-Host
+ Write-Output ""
+ Write-Output "Building solution in $script:Config build"
+ Write-Output ==================================================
+ Write-Output ""
dotnet build $script:Solution -c $script:Config
RestoreForegroundColor # The dotnet call may change the foreground color, e. g. when displaying test result exceptions.
@@ -276,10 +276,10 @@ function BuildSolution()
# Loads DllInfo objects for all DLLs of the solution to run dotnet test for and saves the Windows and Linux specific lists.
function LoadTestDllInfos()
{
- Write-Host
- Write-Host "Analyzing Solution to find test DLLs"
- Write-Host ==================================================
- Write-Host
+ Write-Output ""
+ Write-Output "Analyzing Solution to find test DLLs"
+ Write-Output ==================================================
+ Write-Output ""
$dllInfos = GetDllInfos
@@ -352,9 +352,9 @@ function OutputTestDlls($testDllInfos, $systemName)
{
$testDllsOutput = $testDllInfos | Select-Object -Property DllFileName, EnvironmentName, DllFolder
- Write-Host "DLLs to test in $systemName found in `"$script:Solution`":"
- Write-Host ----------------------------------------
- Write-Host ($testDllsOutput | Format-Table | Out-String)
+ Write-Output "DLLs to test in $systemName found in `"$script:Solution`":"
+ Write-Output ----------------------------------------
+ Write-Output ($testDllsOutput | Format-Table | Out-String)
}
# Gets DllInfo objects for all DLLs of the solution.
@@ -543,9 +543,9 @@ function RunTests()
# Runs dotnet test for all needed DLLs for the given $systemName.
function RunTestsForSystem($testDllInfos, $systemName, $isHostedWsl)
{
- Write-Host
- Write-Host Running tests on $systemName
- Write-Host ==================================================
+ Write-Output ""
+ Write-Output Running tests on $systemName
+ Write-Output ==================================================
foreach ($testDllInfo in $testDllInfos)
{
# Work with absolute DLL path here to avoid errors.
@@ -565,10 +565,10 @@ function RunTestsForSystem($testDllInfos, $systemName, $isHostedWsl)
$TestResultsFilename = GetTestResultsFilename $testDllInfo.EnvironmentName
- Write-Host
- Write-Host ($systemName): dotnet test $testDllInfo.DllFileName ("(" + $testDllInfo.TargetFramework + ")")...
- Write-Host ----------------------------------------
- Write-Host
+ Write-Output ""
+ Write-Output ($systemName): dotnet test $testDllInfo.DllFileName ("(" + $testDllInfo.TargetFramework + ")")...
+ Write-Output ----------------------------------------
+ Write-Output ""
# Change current location to store the trx file in the default "TestResults" folder inside the project folder.
Push-Location $testDllInfo.ProjectFolder
@@ -590,8 +590,8 @@ function RunTestsForSystem($testDllInfos, $systemName, $isHostedWsl)
Pop-Location
}
}
- Write-Host
- Write-Host
+ Write-Output ""
+ Write-Output ""
}
# Gets the name of the environment for the given system and framework.
@@ -662,10 +662,11 @@ function GetTestResultsFilename($environmentName)
# Loads the trx files and displays the test results.
function LoadAndShowTestResults()
{
- Write-Host
- Write-Host
- Write-Host "TestResults" -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output.
- Write-Host "==================================================" -ForegroundColor Green
+ # TODO -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output.
+ Write-Output ""
+ Write-Output ""
+ Write-Output "TestResults"
+ Write-Output "=================================================="
if ($script:Net6)
{
@@ -737,12 +738,13 @@ function LoadAndShowTestResults()
$genericCodeBaseWithExtension = $genericCodeBaseInfo.GenericCodeBase + $genericCodeBaseInfo.GenericCodeBaseExtension
- Write-Host
- Write-Host
+ # TODO -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output.
+ Write-Output ""
+ Write-Output ""
$title = ("CodeBase:").PadRight($padValue + 1, ' ')
- Write-Host $title $genericCodeBaseWithExtension -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output.
- Write-Host "----------------------------------------------------------------------------------------------------" -ForegroundColor Green
- Write-Host Test files:
+ Write-Output $title $genericCodeBaseWithExtension
+ Write-Output "----------------------------------------------------------------------------------------------------"
+ Write-Output Test files:
$codeBaseResults = @() # Collects all results for this GenericCodeBase.
@@ -768,18 +770,18 @@ function LoadAndShowTestResults()
# If the project is not targeting this environment.
if ($notTargetingEnvironment)
{
- Write-Host $title Not implemented.
+ Write-Output $title Not implemented.
}
# If trx file is not found or outdated, output "No test results found" and continue loop.
elseif ($testResultsFileExists -eq $false -or $testResultsFileOutdated)
{
$testResultsFile = $null
- Write-Host $title No test results found.
+ Write-Output $title No test results found.
}
# If trx file is found and from this test run, collect results from the file.
else
{
- Write-Host $title $testResultsFile
+ Write-Output $title $testResultsFile
# Read trx content.
[xml]$fileContent = Get-Content -Path $testResultsFile
@@ -857,11 +859,12 @@ function LoadAndShowTestResults()
# Add summary for all grouped test results.
- Write-Host
- Write-Host
- Write-Host
- Write-Host Summary -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output.
- Write-Host "----------------------------------------------------------------------------------------------------" -ForegroundColor Green
+ # TODO -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output.
+ Write-Output ""
+ Write-Output ""
+ Write-Output ""
+ Write-Output Summary
+ Write-Output "----------------------------------------------------------------------------------------------------"
# Create test summary from grouped test results.
$summary = CreateTestSummary $allGroupedResults $environmentNames
diff --git a/dev/zip-PDFsharp.ps1 b/dev/zip-PDFsharp.ps1
new file mode 100644
index 00000000..0e8f414e
--- /dev/null
+++ b/dev/zip-PDFsharp.ps1
@@ -0,0 +1,38 @@
+# Compresses all files of PDFsharp into a PDFsharp.zip file
+
+#Requires -Version 7
+#Requires -PSEdition Core
+
+Push-Location "$PSScriptRoot/.."
+
+try {
+ # $root = "$PSScriptRoot/.."
+ Remove-Item zip -Recurse -Force -ErrorAction SilentlyContinue
+ Remove-Item PDFsharp.zip -Force -ErrorAction SilentlyContinue
+ # New-Item -ItemType Directory -Force -Path $Root/PDFsharp.zip
+ New-Item -ItemType Directory -Force -Path zip
+
+ # I tried an hour or so to do it with Get-Childitem. No way, I finally use robocopy...
+ # robocopy . $root/zip *.* /dcopy:t /e /xa:h /xd .git /xd "bin" /xd "obj" /xd assets /xd zip
+ robocopy . zip/PDFsharp *.* /dcopy:t /e /xa:h /xd .git /xd .vs /xd .vscode /xd "bin" /xd "obj" /xd assets /xd zip `
+ /xd TestResults /xd localtests `
+ /xf debugSettings.json /xf *.user /xf *.userprefs /xf launchsettings.json /xf testEnvironments.json
+ # robocopy from to *.* /dcopy:t /e /xa:h /xd "System Volume Information" /xd "$RECYCLE*"
+
+ # Delete generated files
+ Remove-Item zip/PDFsharp/src/SemVersion.props -ErrorAction SilentlyContinue
+ Remove-Item zip/PDFsharp/src/PdfSharpBuildConfig.props -ErrorAction SilentlyContinue
+ Remove-Item zip/PDFsharp/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SSemVersionInformation-generated.cs -ErrorAction SilentlyContinue
+
+ # Get-ChildItem zip -Recurse | measure
+
+ Push-Location "zip"
+ Compress-Archive -Path PDFsharp -DestinationPath ../PDFsharp.zip -CompressionLevel Fastest -Force
+ Pop-Location
+
+ # Remove-Item $root/zip -Recurse -Force -ErrorAction SilentlyContinue
+ Remove-Item zip -Recurse -Force -ErrorAction SilentlyContinue
+}
+finally {
+ Pop-Location
+}
diff --git a/docs/Chars.md b/docs/Chars.md
deleted file mode 100644
index bce9e51e..00000000
--- a/docs/Chars.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Characters
-
- “” „“ ’ ‘’ ‚‘ »« ›‹ –
- · × ² ³ ½ € † …
- ✔ ✘ ↯ ± − × ÷ ⋅ √ ≠ ≤ ≥ ≡
- ® © ← ↑ → ↓ ↔ ↕ ∅
-
-# Use of typographic quotation marks
-
-“English” | „German“ | ‘English’ | ‚German‘
-
-Lower left single quotation mark vs comma: ‚ vs ,
-The glyph looks different e.g. in Segoe UI.
diff --git a/docs/MigraDoc/AboutLineSpacing.md b/docs/MigraDoc/AboutLineSpacing.md
deleted file mode 100644
index 466c1850..00000000
--- a/docs/MigraDoc/AboutLineSpacing.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Line spacing
-
-This article is about the definition of line spacing in MigraDoc.
-
-## Line positioning
-
-The line positioning, depending on the line spacing, should be identical to the positioning done by Microsoft Word.
-
-The baseline is calculated as follows.
-
-### Relative line spacings
-
-For a line’s baseline position in a paragraph using the **Single**, **OnePtFive**, **Double** or **Multiple** line spacing,
-the **maximum** of the **paragraph’s baseline** and all **FormattedText’s baselines** is used.
-
-All baselines used for calculation are the result of the used **font size multiplied with the given line spacing value**.
-
-### Absolute line spacings
-
-#### LineSpacingRule.AtLeast
-
-For a line’s baseline position in a paragraph using the **AtLeast** line spacing,
-the **maximum** of the **paragraph’s baseline** and all **FormattedText’s baselines** is used.
-
-All baselines used for calculation are the **maximum** of the **used font size** and the **paragraph’s line spacing value**.
-
-#### LineSpacingRule.Exactly
-
-For a line’s baseline position in a paragraph using the **Exactly** line spacing,
-**only the paragraph’s font and line spacing** are considered.
-This way, FormattedTexts with a larger font size have no impact on the line’s baseline position.
-
-The baseline is calculated by using the **paragraph’s font baseline scaled to the paragraph’s line spacing**.
diff --git a/docs/MigraDoc/change-log/MD-v6.2.1-log.md b/docs/MigraDoc/change-log/MD-v6.2.1-log.md
new file mode 100644
index 00000000..dfdb779d
--- /dev/null
+++ b/docs/MigraDoc/change-log/MD-v6.2.1-log.md
@@ -0,0 +1,17 @@
+# MigraDoc 6.2.1 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`.
+
+## What’s new in version 6.2.1
+
+### Breaking changes
+
+*(none)*
+
+### Features
+
+The new formatting option of PDFsharp is also useful when generating PDF files from MigraDoc.
+
+### Issues
+
+The bug fixes of PDFsharp are also useful when generating PDF files from MigraDoc.
diff --git a/docs/MigraDoc/change-log/MD-v6.2.2-log.md b/docs/MigraDoc/change-log/MD-v6.2.2-log.md
new file mode 100644
index 00000000..9b62ac2e
--- /dev/null
+++ b/docs/MigraDoc/change-log/MD-v6.2.2-log.md
@@ -0,0 +1,17 @@
+# MigraDoc 6.2.2 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`.
+
+## What’s new in version 6.2.2
+
+### Breaking changes
+
+*(none)*
+
+### Features
+
+*(none)*
+
+### Issues
+
+The bug fixes of PDFsharp are also useful when generating PDF files from MigraDoc.
diff --git a/docs/MigraDoc/change-log/MD-v6.2.3-log.md b/docs/MigraDoc/change-log/MD-v6.2.3-log.md
new file mode 100644
index 00000000..216ec71e
--- /dev/null
+++ b/docs/MigraDoc/change-log/MD-v6.2.3-log.md
@@ -0,0 +1,17 @@
+# MigraDoc 6.2.3 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`.
+
+## What’s new in version 6.2.3
+
+### Breaking changes
+
+*(none)*
+
+### Features
+
+*(none)*
+
+### Issues
+
+The bug fixes of PDFsharp are also useful when generating PDF files from MigraDoc.
diff --git a/docs/MigraDoc/change-log/MD-v6.2.4-log.md b/docs/MigraDoc/change-log/MD-v6.2.4-log.md
new file mode 100644
index 00000000..c0155815
--- /dev/null
+++ b/docs/MigraDoc/change-log/MD-v6.2.4-log.md
@@ -0,0 +1,27 @@
+# MigraDoc 6.2.4 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`.
+
+## What’s new in version 6.2.4
+
+### Breaking changes
+
+*(none)*
+
+### Features
+
+*(none)*
+
+### Issues
+
+**MigraDoc shows filename in PDF if image cannot be found**
+If an image cannot be found, MigraDoc renders a placeholder for that image in PDF.
+The placeholder now shows the path of the image that cannot be found.
+
+**Fixed MigraDoc memory leak after rendering PDF**
+With MigraDoc 6.2.3, a reference to the MigraDoc Document was kept after rendering PDF.
+Only a reference to the last document was kept.
+So, when rendering 10 documents, 9 documents were released.
+With MigraDoc 6.2.4, the MigraDoc Document is released after rendering.
+
+The bug fixes of PDFsharp are also useful when generating PDF files from MigraDoc.
diff --git a/docs/MigraDoc/change-log/MD-v6.3.0-log.md b/docs/MigraDoc/change-log/MD-v6.3.0-log.md
new file mode 100644
index 00000000..01a8aab3
--- /dev/null
+++ b/docs/MigraDoc/change-log/MD-v6.3.0-log.md
@@ -0,0 +1,19 @@
+# MigraDoc 6.3.0 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`.
+
+## What’s new in version 6.3
+
+**This file is not yet up-to-date.**
+
+### Breaking changes
+
+*No changes in this version.*
+
+### Features
+
+*No changes in this version.*
+
+### Issues
+
+*No changes in this version.*
diff --git a/docs/MigraDoc/change-log/MD-v6.4.0-log.md b/docs/MigraDoc/change-log/MD-v6.4.0-log.md
new file mode 100644
index 00000000..526fa482
--- /dev/null
+++ b/docs/MigraDoc/change-log/MD-v6.4.0-log.md
@@ -0,0 +1,20 @@
+# MigraDoc 6.4.0 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`.
+
+## What’s new in version 6.4
+
+**This file is not yet up-to-date.**
+
+### Breaking changes
+
+**PdfDate**???
+{according to PDFsharp 6.4.0 change log}
+
+### Features
+
+*No changes in this version.*
+
+### Issues
+
+*No changes in this version.*
diff --git a/docs/MigraDoc/change-log/MD-v7.0.0-preview-log.md b/docs/MigraDoc/change-log/MD-v7.0.0-preview-log.md
new file mode 100644
index 00000000..c0737d05
--- /dev/null
+++ b/docs/MigraDoc/change-log/MD-v7.0.0-preview-log.md
@@ -0,0 +1,23 @@
+# MigraDoc 7.0.0 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`.
+
+## What’s new in version 7.0
+
+**This file is not yet up-to-date.**
+
+### Breaking changes
+
+**PdfDate**
+PdfDate is now based on DateTimeOffset. See PDFsharp 7.0.0 change log for details.
+
+**MigraDocRenderingBuildInformation removed**
+Use class **SemVersionInformation** or **PdfSharp.Capabilities.Build** to get most of the values.
+
+### Features
+
+*No changes in this version.*
+
+### Issues
+
+*No changes in this version.*
diff --git a/docs/MigraDoc/design/PDF-A/PDF-A-Fundamentals.md b/docs/MigraDoc/design/PDF-A/PDF-A-Fundamentals.md
deleted file mode 100644
index 7bd34ef9..00000000
--- a/docs/MigraDoc/design/PDF-A/PDF-A-Fundamentals.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# MigraDoc PDF/A fundamentals
-
-MigraDoc should optionally produce PDF/A-conforming PDF documents.
-
-## Based on PDFsharp
-
-Configure Renderer according to PDFsharp.
diff --git a/docs/MigraDoc/design/PDF-UA/PDF-UA-Fundamentals.md b/docs/MigraDoc/design/PDF-UA/PDF-UA-Fundamentals.md
deleted file mode 100644
index 7baabae7..00000000
--- a/docs/MigraDoc/design/PDF-UA/PDF-UA-Fundamentals.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# PDF/UA fundamentals
-
-MigraDoc should optionally produce PDF/UA-conforming PDF documents.
-
-## Based on PDFsharp
-
-Configure Renderer according to PDFsharp.
-
-## DOM
-
-DOM needs some extensions
-
-* Language tag for
- **Document**, **Paragraph**, **FormattedText**
- but not for
- **Section**, **Table**, etc. They inherit from **Document**.
-
-* Alternate text for images
-
-## Render process
-
-Because DOM well-defines the structure tree, it should be an easy top-down implementation.
diff --git a/docs/PDFsharp/change-log/PS-v6.2.1-log.md b/docs/PDFsharp/change-log/PS-v6.2.1-log.md
new file mode 100644
index 00000000..c40e6171
--- /dev/null
+++ b/docs/PDFsharp/change-log/PS-v6.2.1-log.md
@@ -0,0 +1,38 @@
+# PDFsharp 6.2.1 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`.
+
+## What’s new in version 6.2
+
+### Breaking changes
+
+*(none)*
+
+### Features
+
+**Formatting options for PDF files**
+Using the *Options.Layout* member of the **PdfDocument** class, you can set how PDF files will be generated.
+Use **PdfWriterLayout.Verbose** to get readable output. This is the default for Debug builds of PDFsharp.
+Use **PdfWriterLayout.Compact** to get small output files. This is the default for Release builds of PDFsharp and should be used for production code.
+
+### Issues
+
+**Encrypted hyperlinks are now working**
+We fixed a bug that caused PDF file encryption to throw an exception if the PDF file contained certain types of hyperlinks.
+
+**Character escaping for PDF names**
+**PdfName** now correctly includes the curly braces ('{' and '}') in the list of characters to be escaped.
+
+**Fixed incorrect number formatting**
+This bug could lead to incorrectly formatted Outline entries.
+
+**Fixed exception that occurred for files of size 1030 bytes**
+PDFsharp can now open files with a size of 1030 bytes.
+
+**Arrays in PDF files are now written without line feeds**
+When using **PdfWriterLayout.Compact**, arrays in PDF files will be written without line feeds.
+The files with line feeds are technically correct, but we were informed that some printer software cannot handle those files correctly.
+
+**Keep both /A and /Dest entries for outlines in PDF**
+According to the PDF Reference 1.7, an /A entry is not permitted if a /Dest entry is present.
+PDFsharp 6.2.1 no longer deletes the /A entry if both are present.
diff --git a/docs/PDFsharp/change-log/PS-v6.2.2-log.md b/docs/PDFsharp/change-log/PS-v6.2.2-log.md
new file mode 100644
index 00000000..5937ddaf
--- /dev/null
+++ b/docs/PDFsharp/change-log/PS-v6.2.2-log.md
@@ -0,0 +1,26 @@
+# PDFsharp 6.2.2 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`.
+
+## What’s new in version 6.2.2
+
+### Breaking changes
+
+*(none)*
+
+### Features
+
+*(none)*
+
+### Issues
+
+**Fixed problem with JPEG images from MemoryStreams**
+In special cases, JPEG images from a MemoryStream were not handled correctly. GitHub #303
+
+**Avoid crash on ID duplicates in an object stream**
+PDF files with duplicate IDs in an object stream were not handled correctly.
+We consider such files corrupted, but PDFsharp should now handle them correctly. GitHub #296
+
+**Fixed problems with signatures in DEBUG build**
+The Debug build of PDFsharp adds comments to the generated PDF files.
+This caused problems with the Signature field in PDFs, but was fixed now.Github #293
diff --git a/docs/PDFsharp/change-log/PS-v6.2.3-log.md b/docs/PDFsharp/change-log/PS-v6.2.3-log.md
new file mode 100644
index 00000000..afd2054f
--- /dev/null
+++ b/docs/PDFsharp/change-log/PS-v6.2.3-log.md
@@ -0,0 +1,23 @@
+# PDFsharp 6.2.3 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`.
+
+## What’s new in version 6.2.3
+
+### Breaking changes
+
+*(none)*
+
+### Features
+
+*(none)*
+
+### Issues
+
+**Fixed problem with JPEG images from XImage.FromGdiPlusImage**
+JPEG images created from GDI+ images were not handled correctly. GitHub #318
+This API is only available in the GDI+ build of PDFsharp.
+It is recommended to avoid the *XImage.FromGdiPlusImage* API and use *XImage.FromFile* or *XImage.FromStream* instead if possible.
+
+**Avoid exception when reading certain corrupted files**
+An incorrectly formatted logging string caused an exception when logging a warning about some corrupted PDF files. GitHub #290
diff --git a/docs/PDFsharp/change-log/PS-v6.2.4-log.md b/docs/PDFsharp/change-log/PS-v6.2.4-log.md
new file mode 100644
index 00000000..1e8b7379
--- /dev/null
+++ b/docs/PDFsharp/change-log/PS-v6.2.4-log.md
@@ -0,0 +1,29 @@
+# PDFsharp 6.2.4 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`.
+
+## What’s new in version 6.2.4
+
+### Breaking changes
+
+*(none)*
+
+### Features
+
+**Optional /Length entry for encryption added always**
+The PDF viewer of Edge fails to open some encrypted PDF files if the /Length entry is not present. GitHub #261
+We now always add the /Length entry even in cases where it is optional according to PDF specifications.
+
+### Issues
+
+**CArray written without spaces**
+When editing a content stream with an array, that CArray will now be saved with spaces. GitHub #300
+
+**JPEG issue resolved**
+JPEG JFIF files use "byte stuffing" and 0xff bytes may be followed by 0x00 bytes that must be ignored.
+PDFsharp 6.2.4 now handles this 0xff 0x00 combination where PDFsharp up to 6.2.3 caused an exception.
+GitHub #304, #309, #310
+
+**Indirect DecodeParms now handled correctly**
+PDFsharp 6.2.3 caused an exception if DecodeParms where specified as an indirect object. GitHub #323
+This indirection is valid, but very unusual. Fixed with 6.2.4.
diff --git a/docs/PDFsharp/change-log/PS-v6.3.0-log.md b/docs/PDFsharp/change-log/PS-v6.3.0-log.md
new file mode 100644
index 00000000..1b7baa9f
--- /dev/null
+++ b/docs/PDFsharp/change-log/PS-v6.3.0-log.md
@@ -0,0 +1,46 @@
+# PDFsharp 6.3.0 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`.
+
+## What’s new in version 6.3
+
+**This file is not yet up-to-date.**
+
+### Breaking changes
+
+**PdfDictionary enumerator**
+=> PdfItem? -> PdfItem
+
+**xxxx**
+xxxx.
+
+### Features
+
+* PDF object model revised
+*
+
+**PDF/A enhancements**
+* PdfAManager
+
+**File embedding**
+* **FileManager**
+
+**XMP metadata**
+* xxx
+
+**PdfDate**
+**PdfDate** now based on **DateTimeOffset** internally and corrects minor issues caused by
+differences between .NET and .NET Framework.
+
+**PdfName**
+**PdfName** now based on new class **Name** and fixed some minor issues related to UTF-8 encoding,
+escaping delimiter characters, and handling of the empty name.
+
+**PdfDictionary**
+Now implements IEnumerable> and not
+IEnumerable> anymore.
+
+### Issues
+
+**PdfGraphicsState fixes**
+Usually not relevant as the error affected only drawing when starting with transparent strokes and fills. (GitHub #281)
diff --git a/docs/PDFsharp/change-log/PS-v6.4.0-log.md b/docs/PDFsharp/change-log/PS-v6.4.0-log.md
new file mode 100644
index 00000000..701db4ae
--- /dev/null
+++ b/docs/PDFsharp/change-log/PS-v6.4.0-log.md
@@ -0,0 +1,57 @@
+# PDFsharp 6.4.0 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`.
+
+## What’s new in version 6.4
+
+**This file is not yet up-to-date.**
+
+### Breaking changes
+
+**PdfDictionary enumerator**
+=> PdfItem? -> PdfItem
+
+**PdfDate**
+* PdfDate is now based on DateTimeOffset
+* Value is now nullable in getters
+* tostring returns "" if null
+* Still trailing ‘'’ if not ‘Z’.
+* Change in MigraDoc accordingly
+
+**PdfRectangle**
+* GetRectangle is nullable*
+
+**minor changes**
+* Some PDF annotations lost their default constructor because the constructor now requires a PDF document parameter.
+
+
+### Features
+
+* PDF object model revised
+*
+
+**PDF/A enhancements**
+* PdfAManager
+
+**File embedding**
+* **FileManager**
+
+**XMP metadata**
+* preserve, update
+
+**PdfDate**
+**PdfDate** now based on **DateTimeOffset** internally and corrects minor issues caused by
+differences between .NET and .NET Framework. Fix in 6.3.0
+
+**PdfName**
+**PdfName** now based on new class **Name** and fixed some minor issues related to UTF-8 encoding,
+escaping delimiter characters, and handling of the empty name.
+
+**PdfDictionary**
+Now implements IEnumerable> and not
+IEnumerable> anymore.
+
+### Issues
+
+**PdfGraphicsState fixes**
+Usually not relevant as the error affected only drawing when starting with transparent strokes and fills. (GitHub #281)
diff --git a/docs/PDFsharp/change-log/PS-v7.0.0-preview-log.md b/docs/PDFsharp/change-log/PS-v7.0.0-preview-log.md
new file mode 100644
index 00000000..04ecd057
--- /dev/null
+++ b/docs/PDFsharp/change-log/PS-v7.0.0-preview-log.md
@@ -0,0 +1,80 @@
+# PDFsharp 7.0.0 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`.
+
+## What’s new in version 7.0
+
+### Breaking changes
+
+**PdfDictionary enumerator**
+Item is no longer nullable, so **PdfItem?** became **PdfItem**.
+
+**PdfRectangle** changes
+**PdfRectangle.Empty** is now obsolete and throws an exception.
+Use `new PdfRectangle()` to initialize empty values and to compare if a result of a call is the empty rectangle.
+
+**MediaBox**, **CropBox**, **BleedBox**, **ArtBox**, **TrimBox** changes
+Now obsolete: MediaBoxReadOnly, CropBoxReadOnly, BleedBoxReadOnly, ArtBoxReadOnly, TrimBoxReadOnly.
+Use **HasMediaBox**, **HasCropBox**, etc. to check if the boxes have been set.
+Use **EffectiveCropBoxReadOnly**, **EffectiveTrimBoxReadOnly**, etc. to get the effective value.
+
+**PdfDate**
+* PdfDate is now based on DateTimeOffset
+* Value is now nullable in getters
+* ToString returns "" if null
+* Still trailing ‘'’ if not ‘Z’
+* Changed in MigraDoc accordingly
+
+**PdfRectangle**
+* GetRectangle is nullable
+
+**PdfAnnotation**
+* Keys were moved from the base class **PdfAnnotation** to the specific class where they belong.
+ For example, the `/A` key can now be found under `PdfLinkAnnotation.Keys.A`.
+ Other keys were moved to the **PdfMarkupAnnotation** class.
+
+**Minor changes**
+* Some PDF annotation classes lost their default constructor because the constructor now requires a PDF document parameter.
+* Class **PdfRubberStampAnnotation** renamed to **PdfStampAnnotation**.
+* Enum **PdfRubberStampAnnotationIcon** renamed to **PdfStampAnnotationIcons**.
+* Enum **PdfTextAnnotationIcon** renamed to **PdfTextAnnotationIcons**.
+
+### Features
+
+* PDF object model revised.
+* PDFsharp is now more forgiving when opening certain non-conforming PDF files.
+
+**PDF/A enhancements**
+* PdfAManager
+
+**PDF Forms enhancements**
+* New classes to support Forms and form elements
+* PsX Forms makes using Forms much easier, see https://www.pdfsharp.com/Offers
+
+**File embedding**
+* New **FileManager**
+
+**PDF Annotations enhancements**
+* New classes to support Annotations
+* Outlook: PsX Annotations (coming soon) will make using annotations much easier, see https://www.pdfsharp.com/Offers
+
+**XMP metadata**
+* New class MetadataManager
+* DocumentMetadataStrategy: KeepExisting, AutoGenerate, UserGenerated, NoMetadata
+
+**PdfDate**
+**PdfDate** now based on **DateTimeOffset** internally and corrects minor issues caused by
+differences between .NET and .NET Framework. Fixed in 6.3.0
+
+**PdfName**
+**PdfName** now based on new class **Name** and fixed some minor issues related to UTF-8 encoding,
+escaping delimiter characters, and handling of the empty name.
+
+**PdfDictionary**
+Now implements IEnumerable> and not
+IEnumerable> anymore.
+
+### Issues
+
+**PdfGraphicsState fixes**
+Usually not relevant as the error affected only drawing when starting with transparent strokes and fills. (GitHub #281)
diff --git a/docs/PDFsharp/design/FontSeletion/FontResolver.md b/docs/PDFsharp/design/FontSeletion/FontResolver.md
deleted file mode 100644
index 801b8aff..00000000
--- a/docs/PDFsharp/design/FontSeletion/FontResolver.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# Font selection
-
-Font selection is done by a **FontResolver**.
-
-## Windows platforms
-
-The GDI+ and the WPF builds use GDI+ / WPF functionality to select a font
-if no Custom FontResolver is set. If a Custom FontResolver is set, it is always used.
-The behavior is the same for .NET and .NET Framework.
-
-The Core build for .NET Framework runs only under Windows and therefore it uses the
-WindowsFontResolver (see below) if no Custom FontResolver is set.
-
-The Core build for .NET executed under Windows uses the
-WindowsFontResolver (see below) if no Custom FontResolver is set.
-
-### The Windows Font
-
-## Non-Windows platforms
-
-The Core build for .NET always needs a Custom FontResolver.
-
-### WSL2
-
-PDFsharp prior to version 6.2 uses the `C:\Windows\Fonts` folder.
-We removed this behavior.
-
-### Desktop Linux
-
-A Linux distribution with UI has fonts installed, but there is no (or at least no easy)
-way to locate them on a particular distribution.
-
-### Mac
-
-We have no Macs at empira Software.
-You must write your own FontResolver.
-
-### Linux Docker Container
-
-A Linux distribution for a Docker Container may not provide fonts.
-You must write your own FontResolver.
-
-### Mobile devices, Rasperry Pi, etc.
-
-You must write your own FontResolver.
diff --git a/docs/PDFsharp/design/Issues.md b/docs/PDFsharp/design/Issues.md
deleted file mode 100644
index defc067e..00000000
--- a/docs/PDFsharp/design/Issues.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Issues
-
-Design issues in PDFsharp to ponder on.
-
-## TLS for imported documents
-
-Importing a PDF document depends on TLS. Thread affinity may be problematic since we
-have an execution context.
-It may be better to save the weak document handles in the Globals and protect access with
-a semaphore. But another global lock may lead to undiscoverable dead-lock situations.
diff --git a/docs/PDFsharp/design/PDF-A/PDF-A-Fundamentals.md b/docs/PDFsharp/design/PDF-A/PDF-A-Fundamentals.md
deleted file mode 100644
index 871f6b9c..00000000
--- a/docs/PDFsharp/design/PDF-A/PDF-A-Fundamentals.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# PDF/A fundamentals
-
-PDFsharp should optionally produce PDF/A-conforming documents.
-
-## PDF/A, structure tree, and PDF/UA
-
-Ob der Structure Tree in PDF/A benötigt wird, ist von der gewählten Konformitätsstufe abhängig:
-* Stufe A (Accessible) benötigt den Structure Tree erwartungsgemäß.
-* Stufe B (Basic) kommt ohne Structure Tree aus.
-* Stufe U (Unicode) kommt wahrscheinlich auch ohne Structure Tree aus, habe ich bisher aber nicht überprüft.
-
-Die Standards und Konformitätsstufen sind unter [PDFA-kompakt-20.pdf](https://pdfa.org/wp-content/uploads/2013/05/PDFA-kompakt-20.pdf) auf Seite 8 gut erklärt.
-
-In Word kann man unabhängig von PDF/A auswählen, ob das Dokument barrierefrei sein soll:
-Bei der obigen Auswahl erzeugt Word ein Dokument, in dem kein StructTreeRoot zu finden ist. In den Metadaten steht 3B und veraPDF validiert es entsprechend mit PDF/A-3B.
-Mit zusätzlichem Häkchen bei der Barrierefreiheit erzeugt Word ein Dokument mit StructTreeRoot. In den Metadaten steht 3A und veraPDF validiert es entsprechend mit PDF/A-3A.
-
-## Proposal
-
-Mein Vorschlag wäre, dass wir uns hier an Word orientieren und ebenfalls die Varianten PDF/A-3B und PDF/A-3A anbieten. Bei Auswahl von PDF/A-3A würde dann der UAManager genutzt und das PDF auch gleich PDF/UA-konform, bei PDF/A-3B nicht.
-
-### PDF/A-3B
-
-Basic level.
-
-Shall we support
-
-### PDF/A-3A
-
-### PDF/A-4*
-
-Published in 2020 based on PDF 2.0.
-
-## MigraDoc
-
-MigraDoc shall use PDFsharp for all PDF/A requirements.
-See [MigraDoc PDF/A fundamentals](../../../MigraDoc/design/PDF-A/PDF-A-Fundamentals.md).
-
-## Links
-[PDF/A Wikipedia](https://en.wikipedia.org/wiki/PDF/A) ·
-[veraPDF](https://verapdf.org/) ·
-[pdfa.org](https://pdfa.org/archival-pdf/) ·
-[PDFA-kompakt-20.pdf](https://pdfa.org/wp-content/uploads/2013/05/PDFA-kompakt-20.pdf) ·
-[PDFA-forever_1b.pdf](https://pdfa.org/wp-content/uploads/2011/08/PDFA-forever_1b.pdf) ·
-[PDF/A-3 Compliance Issues](https://www.soliddocuments.com/iso-19005-3-compliance.htm) ·
-[PDF/A-2 Compliance Issues](https://www.soliddocuments.com/iso-19005-2-compliance.htm) ·
-[PDF/A-1 Compliance Issues](https://www.soliddocuments.com/iso-19005-1-compliance.htm)
diff --git a/docs/PDFsharp/design/PdfReader/StringsAndNames.md b/docs/PDFsharp/design/PdfReader/StringsAndNames.md
deleted file mode 100644
index 5e63b8f6..00000000
--- a/docs/PDFsharp/design/PdfReader/StringsAndNames.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# String parsing
-
-Parsing strings has grown over the years and needs to be reviewed.
-Collect issues about strings and names here.
-
-## Names
-
-* Spec says a name can contain any character, but parsers generally use e.g. backslash as a delimiter.
-
-## Strings in PDF objects
-
-Check the following:
-
-* Octal characters. Are illegal octal numbers (8 and 9) handled correctly?
-* Escape characters. PDF specs states that the reverse solidus must be ignored.
-* Nesting of '(' and ')'. Is it always handled correctly?
- Should we optimize Writer if parenthesis occurrences are leveled equally? Currently we always escape parenthesis.
-* Hex strings <…>
- * Is encryption always identified correctly?
- * Can it be encoded big endian? Check how Acrobat handles such strings.
- * How to decided if it is 8 or 16 bit. Digest is 8 bit, Unicode is 16 bit.
- * Can it contain glyph indices outside a content stream?
-
-## Strings in content streams
-
-* `(…)` string. Is this ASCII only?
-* `<…>` string. Is this Glyph ID only?
diff --git a/docs/PDFsharp/design/Signatures/CertificateCreation.md b/docs/PDFsharp/design/Signatures/CertificateCreation.md
deleted file mode 100644
index 291f65c4..00000000
--- a/docs/PDFsharp/design/Signatures/CertificateCreation.md
+++ /dev/null
@@ -1,53 +0,0 @@
-# Certificate creation
-
-This file introduces how OpenSSL can be used to create self-signed certificates that can be used to sign PDF files.
-
-## Creating self-signed certificates
-
-OpenSSL is one option to create certificates for signing.
-
-### Self-signed RSA certificates
-
-For testing, you can easily create an RSA certificate using three calls to OpenSSL:
-
-* Generating a private key
-* Generating the self signed certificate
-* Create a PFX file containing certificate and private key
-
-Here is code that creates an RSA certificate with a 2048-bits key:
-
-```pwsh
-openssl genrsa 2048 > private.pem
-openssl req -days 32767 -x509 -new -key private.pem -out public.pem
-openssl pkcs12 -export -in public.pem -inkey private.pem -out mycert.pfx
-```
-
-The *-days* parameter specifies how long the certificate will be valid. For some use cases you should not exceed 397 days.
-
-Note that the three statements above will prompt for input like location, company name, and passwords.
-
-### Self-signed DSA certificates
-
-Theoretically, you can create DSA certificates using four calls to OpenSSL. Practically, we did not have success and all DSA certificates we created were rejected by Windows as invalid.
-
-The code is very similar to the RSA case, but specifying the key length requires am extra OpenSSL call to create a parameter file.
-
-```pwsh
-openssl dsaparam 2048 > dsaparam.txt
-openssl gendsa dsaparam.txt > private.pem
-openssl req -days 32767 -x509 -new -key private.pem -out public.pem
-openssl pkcs12 -export -in public.pem -inkey private.pem -out mycert.pfx
-```
-
-Please let us know what we were doing wrong if you manage to create DSA certificates using OpenSSL. Thanks in advance.
-
-### Automization of certificate creation
-
-OpenSSL will prompt for information while creating the certificate.
-To avoid this, the *subj* parameter can be used:
-
-```txt
--subj "/C=US/ST=Washington/L=Seattle/O=John Doe Ltd./OU=PR/CN=John Doe"
-```
-
-Alternatively, you can write the answers into a text file and redirect input to that file.
diff --git a/docs/PDFsharp/docs/Documentation.md b/docs/PDFsharp/docs/Documentation.md
new file mode 100644
index 00000000..c6506086
--- /dev/null
+++ b/docs/PDFsharp/docs/Documentation.md
@@ -0,0 +1,14 @@
+# Notes to source code documentation
+
+## PDFsharp PDF classes
+
+// Reference 2.0: 12.5.2 Annotation dictionaries / Page 467
+// Reference 2.0: xxx / Page xxx
+
+// Reference 2.0: Table 166 — Entries common to all annotation dictionaries / Page 467
+// Reference 2.0: xxx / Page xxx
+
+// ReSharper disable InconsistentNaming
+
+// ReSharper restore InconsistentNaming
+
diff --git a/docs/change-log/gn-v6.2.0-preview-log.md b/docs/change-log/gn-v6.2.0-preview-log.md
index 4796d252..c513a61f 100644
--- a/docs/change-log/gn-v6.2.0-preview-log.md
+++ b/docs/change-log/gn-v6.2.0-preview-log.md
@@ -26,7 +26,7 @@ For better performance, set 'publiclyVisible' to true when creating the MemorySt
#### PDFsharp issues
**Lexer.ScanNumber and CLexer.ScanNumber**
-Based on a bug that crashes PDFsharp if a number in a PDFfile has to much leading zeros, we revised the code of **Lexer.ScanNumber** and **CLexer.ScanNumber**.
+Based on a bug that crashes PDFsharp if a number in a PDFfile has too many leading zeros, we revised the code of **Lexer.ScanNumber** and **CLexer.ScanNumber**.
**No more commas allowed in XUnit**
An old hack allows **XUnit** to be assigned from a string that uses a comma as decimal separator.
diff --git a/docs/change-log/gn-v6.2.1-log.md b/docs/change-log/gn-v6.2.1-log.md
new file mode 100644
index 00000000..518a8c5e
--- /dev/null
+++ b/docs/change-log/gn-v6.2.1-log.md
@@ -0,0 +1,20 @@
+# General 6.2.1 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **general** `History.md`.
+
+## What’s new in version 6.2.1
+
+### Breaking changes
+
+*(none)*
+
+### General features
+
+The new formatting option of PDFsharp is useful when generating PDF files from PDFsharp or MigraDoc.
+
+**Automatic download of assets folder**
+TODO
+
+### General issues
+
+The bug fixes of PDFsharp are useful when generating PDF files from PDFsharp or MigraDoc.
diff --git a/docs/change-log/gn-v6.2.2-log.md b/docs/change-log/gn-v6.2.2-log.md
new file mode 100644
index 00000000..15466d90
--- /dev/null
+++ b/docs/change-log/gn-v6.2.2-log.md
@@ -0,0 +1,17 @@
+# General 6.2.2 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **general** `History.md`.
+
+## What’s new in version 6.2.2
+
+### Breaking changes
+
+*(none)*
+
+### General features
+
+*(none)*
+
+### General issues
+
+The bug fixes of PDFsharp are useful when generating PDF files from PDFsharp or MigraDoc.
diff --git a/docs/change-log/gn-v6.2.3-log.md b/docs/change-log/gn-v6.2.3-log.md
new file mode 100644
index 00000000..be892714
--- /dev/null
+++ b/docs/change-log/gn-v6.2.3-log.md
@@ -0,0 +1,22 @@
+# General 6.2.3 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **general** `History.md`.
+
+## What’s new in version 6.2.3
+
+### Breaking changes
+
+With version 6.2.3, we added support for .NET 9 and .NET 10.
+Version 6.2.3 no longer compiles against .NET 6 which is out of support.
+The NuGet packages can still be used for applications that use .NET 6.
+Version 6.2.3 was built using Visual Studio 2026.
+It still compiles with Visual Studio 2022, provided the .NET 10 SDK is installed, but warnings will be shown.
+You can remove .NET 10 from the list of target frameworks if you do not need it.
+
+### General features
+
+*(none)*
+
+### General issues
+
+The bug fixes of PDFsharp are useful when generating PDF files from PDFsharp or MigraDoc.
diff --git a/docs/change-log/gn-v6.2.4-log.md b/docs/change-log/gn-v6.2.4-log.md
new file mode 100644
index 00000000..df14f1f0
--- /dev/null
+++ b/docs/change-log/gn-v6.2.4-log.md
@@ -0,0 +1,18 @@
+# General 6.2.4 change log
+
+A copy of the text below this line is added to `docs.pdfsharp.net` **general** `History.md`.
+
+## What’s new in version 6.2.4
+
+### Breaking changes
+
+*(none)*
+
+### General features
+
+**Dependency on Microsoft.Extensions.Logging removed**
+The new version depends on "Microsoft.Extensions.Logging.Abstractions" instead. GitHub #325
+
+### General issues
+
+The bug fixes of PDFsharp are useful when generating PDF files from PDFsharp or MigraDoc.
diff --git a/docs/change-log/gn-v6.3.0-log.md b/docs/change-log/gn-v6.3.0-log.md
new file mode 100644
index 00000000..17280c7f
--- /dev/null
+++ b/docs/change-log/gn-v6.3.0-log.md
@@ -0,0 +1,33 @@
+# 6.3.0 change log
+
+This text is copied to `docs.pdfsharp.net`.
+
+## What’s new in version 6.3
+
+**This file is not yet up-to-date.**
+
+
+### Breaking changes
+
+* From 6.2 to 6.3 needs a recompile.
+
+### General features
+
+* GitVersion build task replaced for better build time.
+* Packages for .NET 9.
+* 6.4: Implementation for .NET 9 that does not use obsolete APIs.
+* DownloadAssets is invoked automatically during building. Requires Internet access.
+* 6.4: `PDFsharpBuildFewerFrameworks` build property to speed up development by compiling against .NET 8 only.
+* 6.4: `PDFsharpRunSlowTests` build property to include slow unit tests.
+
+**.NET 9** replaces **.NET 6**
+We added .NET 9 (`net9.0`) to ``.
+We removed .NET 6 because it is no longer supported by Microsoft. Projects using .NET 6 can still use the current version.
+
+
+#### General issues
+
+* TODO
diff --git a/docs/change-log/gn-v7.0.0-preview-log.md b/docs/change-log/gn-v7.0.0-preview-log.md
new file mode 100644
index 00000000..3a2de2e9
--- /dev/null
+++ b/docs/change-log/gn-v7.0.0-preview-log.md
@@ -0,0 +1,34 @@
+# 7.0.0 change log
+
+This text is copied to `docs.pdfsharp.net`.
+
+## What’s new in version 7.0
+
+**This file is not yet up-to-date.**
+
+
+### Breaking changes
+
+* From 6.x to 7.0 needs a recompile.
+
+### General features
+
+* GitVersion build task replaced for better build time.
+* Packages for .NET 9 and .NET 10.
+* 6.4: Implementation for .NET 9 that does not use obsolete APIs.
+* DownloadAssets is invoked automatically during building. Requires Internet access.
+* 6.4: `PDFsharpBuildFewerFrameworks` build property to speed up development by compiling against .NET 10 only.
+* 6.4: `PDFsharpRunSlowTests` build property to include slow unit tests.
+* Code review: many classes were refactored to achieve cleaner code with improved maintainability.
+
+**.NET 9** replaces **.NET 6**
+We added .NET 9 (`net9.0`) and .NET 10 to ``.
+We removed .NET 6 because it is no longer supported by Microsoft. Projects using .NET 6 can still use the current version.
+
+
+#### General issues
+
+* TODO
diff --git a/docs/coding/DevNotes.md b/docs/coding/DevNotes.md
index 03fca80f..4c5f3ae1 100644
--- a/docs/coding/DevNotes.md
+++ b/docs/coding/DevNotes.md
@@ -6,13 +6,13 @@ Just here before being moved to an appropriate place.
## Temporary tags
-Temporary tags are allowed in develop branch, but must be removed in releases.
+Temporary tags are allowed in develop branch, but must be removed in (final) releases.
Create a unit test that fails if a temporary tag is in the C# code.
### TODO
-**Here is something to be done __before the project is released__.**
+**Here is something to be done __before the project is finally released__.**
### BUG
@@ -55,7 +55,7 @@ documentation or reference purposes and shall not be removed.
### IMPROVE
-Here is code that substantially works but has potential for improvements
+Here is correct code that substantially works but has potential for improvements
for better reliability.
**Example**
@@ -78,7 +78,9 @@ Here is code that should be covered by (more) unit tests.
Remove all this stuff from existing code.
* Hack:
-* Note:
+* Note:
+ Write "Note that ..."
+ Do not delete it in text pasted from the PDF Reference.
* Magic:
* ToDo
* UNDONE
diff --git a/docs/development/DevelopmentWithWSL.md b/docs/development/WSL/DevelopmentWithWSL.md
similarity index 76%
rename from docs/development/DevelopmentWithWSL.md
rename to docs/development/WSL/DevelopmentWithWSL.md
index 6286b060..6d289996 100644
--- a/docs/development/DevelopmentWithWSL.md
+++ b/docs/development/WSL/DevelopmentWithWSL.md
@@ -27,6 +27,11 @@ It requires you to set up a launchSettings.json in the properties folder of the
Afterwards, you can choose the profile when starting the project.
+When added for at least one project, Visual Studio may or may not show the WSL launching option for other projects
+and add it automatically when choosing WSL.
+However, adding the file to a project when needed is recommended.
+
+You can **copy the launchsettings.template.json** to Properties/launchSettings.json in your project for a quick start.
## Testing in WSL
@@ -53,6 +58,8 @@ It requires you to set up a testenvironments.json in the root of the solution, w
Afterwards, you can choose the test environment in Test Explorer.
+You can **copy the testenvironments.template.json** to testenvironments.json in the solution root folder for a quick start.
+
### Run run-tests script
With the run-test.ps1 in the dev folder to run all tests in in all available environments, including WSL.
diff --git a/docs/development/WSL/launchsettings.template.json b/docs/development/WSL/launchsettings.template.json
new file mode 100644
index 00000000..2f189c12
--- /dev/null
+++ b/docs/development/WSL/launchsettings.template.json
@@ -0,0 +1,11 @@
+{
+ "profiles": {
+ "Windows": {
+ "commandName": "Project"
+ },
+ "WSL": {
+ "commandName": "WSL",
+ "distributionName": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/development/WSL/testenvironments.template.json b/docs/development/WSL/testenvironments.template.json
new file mode 100644
index 00000000..65570029
--- /dev/null
+++ b/docs/development/WSL/testenvironments.template.json
@@ -0,0 +1,17 @@
+{
+ "version": "1",
+ "environments": [
+ // See https://aka.ms/remotetesting for more details
+ // about how to configure remote environments.
+ {
+ "name": "WSL",
+ "type": "wsl",
+ "wslDistribution": "Ubuntu"
+ },
+ //{
+ // "name": "Docker dotnet/sdk",
+ // "type": "docker",
+ // "dockerImage": "mcr.microsoft.com/dotnet/sdk"
+ //}
+ ]
+}
\ No newline at end of file
diff --git a/docs/docs-dummy.csproj b/docs/docs-public.csproj
similarity index 81%
rename from docs/docs-dummy.csproj
rename to docs/docs-public.csproj
index 2731fd21..5152857c 100644
--- a/docs/docs-dummy.csproj
+++ b/docs/docs-public.csproj
@@ -3,7 +3,7 @@
- net8.0
+ net10.0
diff --git a/docs/publishing/BeforeReleases.md b/docs/publishing/BeforeReleases.md
deleted file mode 100644
index 4bdbbd05..00000000
--- a/docs/publishing/BeforeReleases.md
+++ /dev/null
@@ -1,128 +0,0 @@
-# Before releases
-
-Before you follow the instructions from the Azure DevOps wiki how to publish PDFsharp,
-complete the following tasks.
-
-## General todos
-
-### Update the assembly build version
-
-Set the assembly build version in file `gitversion.yml` to the number of days from
-January 1st 2005 to the release day. **This is essential for users that use intaller tools.**
-
-```yml
-assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 9999}'
-```
-
-### Update and sync used NuGet packages
-
-Update version numbers of NuGet packages used in PDFsharp code (like `MicroSoft.Extensions.Logging`) and
-build tools (xunit, gitversion, ...).
-Do this with `"Manage NuGet Packages for Solution..."` to see what’s new and update the version numbers
-manually in `Directory.Packages.props`.
-
-Sync `Directory.Packages.props` with all other solutions, like `PDFsharp.Samples` etc.
-
-### Check specific files
-
-* Search for **`CHECK_BEFORE_RELEASE`** and verify the code at these places.
-* Check `Directory.Build.targets`
- * `USE_LONG_SIZE` must be defined in a release or preview version.
- * `TEST_CODE_xxx` must be 'undefined' by the suffix `_xxx`.
-
-### Run all tests
-
-* Run all tests in both RELEASE and DEBUG configurations.
-* Run all tests, including all skipped tests, under Windows and WSL2 with `run-tests` (see below).
-* TODO: Run all tests in a Linux Docker Container
-
-#### Run run-test script
-
-Execute `run-test.ps1` with the following parameters:
-
-1. -Config \: Run the script once with "Debug" and once with "Release".
-2. -Net6 \: Set it to $false to run tests for Net8.
-3. -SkipBuild \: Set it to $false to build the solution first.
-4. -RunAllTests \: Set it to $true to run even slow tests.
-
-Run `help .\dev\run-tests.ps1` for more information.
-
-So this is the call for the DEBUG build using .NET 8:
-
-```PWSH
-.\dev\run-tests.ps1 Debug $false $false $true
-```
-
-And this is the call for the RELEASE build using .NET 8:
-
-```PWSH
-.\dev\run-tests.ps1 Release $false $false $true
-```
-
-## Update files and configurations
-
-### Update .md files
-
-* Check and update [README.md](../README.md)
-
-### Update NuGet configuration
-
-* Check/update BoilerplateText.md file - transfer changes to all NuGet projects
-* Check/update referenced projects in NuGet .csproj files
-* Check/update referenced NuGet packages in .nuspec files
-* Check/update text files
-* Check packages with NuGet Package Explorer
-
-## Test other repositories
-
-Rebuild both DEBUG and RELEASE and update local NuGet packages.
-
-### GBE test
-
-Use ComparePdf (part of PDFsharp.Lab) with GBE to compare the "!!TestResult.pdf" of the last release with the current one:
-
-```PWSH
-.\comparepdfs -f "{PathToFolderOfOldFile}\!!TestResult.pdf" "{PathToFolderOfNewFile}\!!TestResult.pdf" -t "{TargetFolder}" --unique-target -n "{ReportName}" -o
-```
-
-The folder with the report will be created in `{TargetFolder}`.
-Due to `--unique-target`, the current date and time will be appended to the target folder and the PDF filename `{ReportName}`.
-The report will be opened after creation, because `-o` is set.
-
-Run `.\comparepdfs --help` for more information.
-
-### PDFsharp IssueSubmissionTemplate
-
-* Build solution
-* Run the applications
-
-### PDFsharp.Samples
-
-* Build solution
-* Run the script that executes all samples
-
-### PDFsharp.Tests
-
-* Build solution
-* Run the tests
-
-### PDFsharp.Lab
-
-* Build solution
-* Run the script that executes all samples
-
-### PDFsharp.MAUI
-
-* Build solution
-* Run the script that executes all samples
-
-### PDFsharp.Blazor
-
-* Build solution
-* Run the script that executes all samples
-
-### Test with QBX
-
-With Qbx.LayouterComparer in AkutQBX.sln you can automatically generate and compare a desired set of the example files with a desired set of layouters with this and a referenced QBX version.
-Usually you will run Qbx.LayouterComparer in the updated QBX repository and reference a repository clone with the last QBX commit before the update to the new PDFsharp version as head.
-Simply adjust the folders in the Program.cs and comment out not desired files and layouters.
diff --git a/docs/publishing/BoilerplateText.md b/docs/publishing/BoilerplateText.md
deleted file mode 100644
index 87ab7c65..00000000
--- a/docs/publishing/BoilerplateText.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Boilerplate text
-
-** UNDER CONSTRUCTION **
-
-## PDFsharp
-
-PDFsharp Project - PDFsharp
-PDFsharp Library - PDFsharp
-MigraDoc Library - Library
-
-
-Source
-```
-// PDFsharp - A .NET library for processing PDF
-```
-
-**Description**
-```
-PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents.
-```
-
-
-PDFsharp is a .NET library for creating and manipulating PDF documents. It provides a simple API for generating PDF documents from scratch or modifying existing ones. It can be used in a variety of applications, such as generating invoices, reports, or other types of documents.
-
-
-
-Stefan
-Describe PDFsharp in 3 sentences
-ChatGPT
-PDFsharp is a .NET library for creating and manipulating PDF documents. It provides a simple API for generating PDF documents from scratch or modifying existing ones. It can be used in a variety of applications, such as generating invoices, reports, or other types of documents.
-
-Thomas
-Describe PDFsharp in 3 simple sentences.
-ChatGPT
-PDFsharp is a library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents.
-
-Stefan
-Describe PDFsharp in 3 simple sentences for women.
-ChatGPT
-PDFsharp is a tool that helps you create and edit PDF documents using .NET. With its simple interface, you can easily generate or modify PDF files for a variety of purposes. Whether you need to create reports, invoices, or other types of documents, PDFsharp can help you get the job done.
-
-Stefan
-Describe PDFsharp in 3 sentences for software developers.
-ChatGPT
-PDFsharp is a .NET library that provides an easy-to-use API for creating and manipulating PDF documents. It supports a wide range of features such as text, images, graphics, and interactive elements. With PDFsharp, developers can quickly generate or modify PDF files for various purposes in their applications.
-
-
-## MigraDoc
-
-```
-// MigraDoc - Creating Documents on the Fly
-```
-
-```
-MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications.
-```
-
-Stefan
-Describe MigraDoc in 3 simple sentences.
-ChatGPT
-MigraDoc is a .NET library that allows developers to create documents such as PDF, RTF, and HTML using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications.
-
-
-**Release notes**
-
-Note: Do not replicate information from the metadata (like "6.0.0" or "prerelease").
-
-PDFsharp:
-This is a version of PDFsharp compatible with .NET 6 and higher.
-The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’.
-The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’.
-We also publish packages that contain PDFsharp plus MigraDoc.
-See the project docs site for further information:
-https://docs.pdfsharp.net/link/readme-v6.2.html
-
-
-PDFsharp & MigraDoc:
-This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher.
-The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’.
-The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’.
-See the project docs site for further information:
-https://docs.pdfsharp.net/link/readme-v6.2.html
diff --git a/docs/publishing/MakeNewReleaseNotes.md b/docs/publishing/MakeNewReleaseNotes.md
deleted file mode 100644
index cafa700c..00000000
--- a/docs/publishing/MakeNewReleaseNotes.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# Files to check
-
-[README.md](..\README.md)
-
-* Test on Linux web server
-
-## Nuget packages
-
-[PDFsharp.NuGet README.md](..\src\foundation\nuget\src\PDFsharp.NuGet\README.md)
-[PDFsharp.NuGet Description.txt](..\src\foundation\nuget\src\PDFsharp.NuGet\Description.txt)
-[PDFsharp.NuGet ReleaseNotes.txt](..\src\foundation\nuget\src\PDFsharp.NuGet\ReleaseNotes.txt)
-
-[PDFsharp.NuGet-gdi README.md](..\src\foundation\nuget\src\PDFsharp.NuGet-gdi\README.md)
-[PDFsharp.NuGet-gdi Description.txt](..\src\foundation\nuget\src\PDFsharp.NuGet-gdi\Description.txt)
-[PDFsharp.NuGet-gdi ReleaseNotes.txt](..\src\foundation\nuget\src\PDFsharp.NuGet-gdi\ReleaseNotes.txt)
-
-[PDFsharp.NuGet-wpf README.md](..\src\foundation\nuget\src\PDFsharp.NuGet-wpf\README.md)
-[PDFsharp.NuGet-wpf Description.txt](..\src\foundation\nuget\src\PDFsharp.NuGet-wpf\Description.txt)
-[PDFsharp.NuGet-wpf ReleaseNotes.txt](..\src\foundation\nuget\src\PDFsharp.NuGet-wpf\ReleaseNotes.txt)
-
-[MigraDoc.NuGet README.md](..\src\foundation\nuget\src\MigraDoc.NuGet\README.md)
-[MigraDoc.NuGet Description.txt](..\src\foundation\nuget\src\MigraDoc.NuGet\Description.txt)
-[MigraDoc.NuGet ReleaseNotes.txt](..\src\foundation\nuget\src\MigraDoc.NuGet\ReleaseNotes.txt)
-
-[MigraDoc.NuGet-gdi README.md](..\src\foundation\nuget\src\MigraDoc.NuGet-gdi\README.md)
-[MigraDoc.NuGet-gdi Description.txt](..\src\foundation\nuget\src\MigraDoc.NuGet-gdi\Description.txt)
-[MigraDoc.NuGet-gdi ReleaseNotes.txt](..\src\foundation\nuget\src\MigraDoc.NuGet-gdi\ReleaseNotes.txt)
-
-[MigraDoc.NuGet-wpf README.md](..\src\foundation\nuget\src\MigraDoc.NuGet-wpf\README.md)
-[MigraDoc.NuGet-wpf Description.txt](..\src\foundation\nuget\src\MigraDoc.NuGet-wpf\Description.txt)
-[MigraDoc.NuGet-wpf ReleaseNotes.txt](..\src\foundation\nuget\src\MigraDoc.NuGet-wpf\ReleaseNotes.txt)
diff --git a/gitversion-6.x.yml b/gitversion-6.x.yml
deleted file mode 100644
index 2fff1bd1..00000000
--- a/gitversion-6.x.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-# Last update: 25-05-19
-assembly-versioning-scheme: MajorMinorPatch
-# PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE
-# Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days;
-# C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days);
-assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7443}'
-strategies: [Mainline]
-assembly-informational-format: '{InformationalVersion}'
-branches:
- develop: # Current development branch
- # branch: develop -- is always 'develop'
- # version: {6.0.0}-develop.123 -- taken from git tag plus 'develop' plus commit count
- regex: ^develop$
- mode: ContinuousDeployment
- increment: None
- label: develop
- # Investigate this currently undocumented breaking change when switching to v6.x.
- # The statement may not be needed.
- # Not in v6.x: prevent-increment-of-merged-branch-version: false
- # Not working: prevent-increment.of-merged-branch: false
- # Not working: prevent-increment.when-branch-merged: false
- track-merge-target: true
- source-branches: ['feature', 'release']
- release: # Release and preview versions.
- # branch: release/6.0.0-preview-3 -- must be same as git tag without leading 'v'.
- # version: {6-0-0-preview-3} -- taken from git tag only
- regex: ^(release[/-]|master)
- # Must not have mode set to be mainline?
- increment: None
- label: ''
- # Not in v6.x: prevent-increment-of-merged-branch-version: true
- track-merge-target: true
- is-release-branch: true
- is-main-branch: true
- source-branches: ['develop', 'release', 'feature']
- feature: # Features and bug fixes.
- # branch: feature/my-new-feature -- arbitrary name, e.g. a new preview
- # version: {6.0.0}-dev-{my-new-feature}.123 -- taken from git tag plus 'dev-' plus branch name plus commit count
- regex: ^(user|feature|fix)[/-]
- mode: ContinuousDeployment
- increment: None
- label: 'dev-{BranchName}'
- # Not in v6.x: prevent-increment-of-merged-branch-version: true
- track-merge-target: true
- source-branches: ['develop', 'release', 'feature']
- pull-request:
- regex: ^(pull|pull\-requests|pr)[/-]
- label: PullRequest
- mode: ContinuousDeployment
-merge-message-formats: {}
diff --git a/gitversion.yml b/gitversion.yml
deleted file mode 100644
index be0052e7..00000000
--- a/gitversion.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-# Last update: 25-05-19
-assembly-versioning-scheme: MajorMinorPatch
-# PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE
-# Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days;
-# C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days);
-assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7443}'
-mode: Mainline
-assembly-informational-format: '{NuGetVersion}'
-branches:
- develop: # Current development branch
- # branch: develop -- is always 'develop'
- # version: {6.0.0}-develop.123 -- taken from git tag plus 'develop' plus commit count
- regex: ^develop$
- mode: ContinuousDeployment
- increment: None
- tag: develop
- prevent-increment-of-merged-branch-version: false
- track-merge-target: true
- source-branches: ['feature', 'release']
- release: # Release and preview versions.
- # branch: release/6.0.0-preview-3 -- must be same as git tag without leading 'v'.
- # version: {6-0-0-preview-3} -- taken from git tag only
- regex: ^(release[/-]|master)
- # Must not have mode set to be mainline?
- increment: None
- tag: ''
- prevent-increment-of-merged-branch-version: true
- track-merge-target: true
- is-release-branch: true
- is-mainline: true
- source-branches: ['develop', 'release', 'feature']
- feature: # Features and bug fixes.
- # branch: feature/my-new-feature -- arbitrary name, e.g. a new preview
- # version: {6.0.0}-dev-{my-new-feature}.123 -- taken from git tag plus 'dev-' plus branch name plus commit count
- regex: ^(user|feature|fix)[/-]
- mode: ContinuousDeployment
- increment: None
- tag: 'dev-{BranchName}'
- prevent-increment-of-merged-branch-version: true
- track-merge-target: true
- source-branches: ['develop', 'release', 'feature']
- pull-request:
- regex: ^(pull|pull\-requests|pr)[/-]
- tag: PullRequest
- mode: ContinuousDeployment
-merge-message-formats: {}
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 00000000..4381b38b
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,5 @@
+# Ignore generated props file
+SemVersion.props
+Local.Build.props
+PdfSharpBuildConfig.props
+PdfSharpBuildConfig.targets
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index ec393bfd..e253e286 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,49 +1,140 @@
+
false
true
+ true
enable
enable
- NU1507
+
+
+
+ $(NoWarn);0507;2070;2087
false
+
+ false
+ false
+ false
+
true
true
-
+
+ false
+
+
+
+ false
+
+
+
-
latest
*.ncrunchproject;*.DotSettings
+
+
+
+
+
+
+
+
+
+
+ net8.0;net9.0;net10.0;netstandard2.0
+ net8.0;net9.0;net10.0
+ net8.0;net9.0;net10.0;net462
+ net8.0;net9.0;net10.0
+ net8.0-windows;net9.0-windows;net10.0-windows;net462
+ net8.0-windows;net9.0-windows;net10.0-windows
+
+
+
+
+ net8.0;net9.0;net10.0;net472
+ net8.0-windows;net9.0-windows;net10.0-windows;net472
+
+
+ net8.0;net9.0;net10.0;netstandard2.0
+ net8.0-windows;net9.0-windows;net10.0-windows;net462
+
+
+
+ $(DefineConstants);FEWER_FRAMEWORKS
+
+
+ false
+
+
+ .Fewer
+
+
+ net10.0
+ net10.0
+ net10.0
+ net10.0
+ net10.0-windows
+ net10.0-windows
+
+
+ net10.0
+ net10.0
+ net10.0-windows
+
+
+ net10.0
+ net10.0-windows
+
+
+
+ $(DefineConstants);RUN_SLOW_TESTS
+
+
+
+
PDFsharp
empira Software
- © 2025 empira
+ © 2026 empira Software GmbH
+ © 2005-2026 empira Software GmbH
+ © 2001-2026 empira Software GmbH
+
PDFsharp Team
empira Software GmbH
+
+ 1032
+ 00240000048000009400000006020000002400005253413100040000010001008794e803e566eccc3c9181f52c4f7044e5442cc2ce3cbba9fc11bc4186ba2e446cd31deea20c1a8f499e978417fad2bc74143a4f8398f7cf5c5c0271b0f7fe907c537cff28b9d582da41289d1dae90168a3da2a5ed1115210a18fdae832479d3e639ca4003286ba8b98dc9144615c040ed838981ac816112df3b5a9e7cab4fbb
+
+
+
+
+
+
- false
+
+
+ $(SemVer)
-
- true
+
+
+ false
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 3e0c2628..76ac5f3e 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -1,7 +1,11 @@
-
- $(DefineConstants);USE_LONG_SIZE;TEST_CODE_xxx
+
+
+
+
+ $(DefineConstants);USE_LONG_SIZE;TEST_CODExxx;PRESERVE_PARSED_VALUESxxx;PDFSHARP_DEBUGxxx
+
\ No newline at end of file
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index b3272e12..dcb63a2f 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -1,39 +1,44 @@
- 8.0.1
- 8.0.1
+ 10.0.5
+ 10.0.5
+ 10.0.5
+ 10.0.5
-
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
-
+
+
-
\ No newline at end of file
diff --git a/src/Local.Build.props---template b/src/Local.Build.props---template
new file mode 100644
index 00000000..711e64c0
--- /dev/null
+++ b/src/Local.Build.props---template
@@ -0,0 +1,17 @@
+
+
+
+
+
+ true
+
+
+
+
+ false
+
+
diff --git a/src/foundation/nuget/src/Directory.Build.props b/src/foundation/nuget/src/Directory.Build.props
index 618588a9..79d9d34d 100644
--- a/src/foundation/nuget/src/Directory.Build.props
+++ b/src/foundation/nuget/src/Directory.Build.props
@@ -29,6 +29,7 @@
+ -DEBUG
$(NuspecProperties);id=$(NuGetId)
$(NuspecProperties);title=$(NuGetTitle)
$(NuspecProperties);config=$(Configuration)
@@ -39,12 +40,16 @@
$(NuspecProperties);owners=$(Owners)
$(NuspecProperties);projectUrl=$(NuGetProjectUrl)
$(NuspecProperties);copyright=$(Copyright)
+ $(NuspecProperties);CopyrightPdfSharp=$(CopyrightPdfSharp)
+ $(NuspecProperties);CopyrightMigraDoc=$(CopyrightMigraDoc)
$(NuspecProperties);releaseNotes=$(NuGetReleaseNotes)
$(NuspecProperties);tags=$(NuGetTags)
$(NuspecProperties);NetCore_PackageVersion=$(NetCore_PackageVersion)
- $(NuspecProperties);Logging_PackageVersion=$(Logging_PackageVersion)
+ $(NuspecProperties);Logging_Abstractions_PackageVersion=$(Logging_Abstractions_PackageVersion)
$(NuspecProperties);Cryptography_PackageVersion=$(Cryptography_PackageVersion)
+
+ $(NuspecProperties);pdfsharpnugetpackagetag=$(PDFsharpNuGetPackageTag)
+
+ false
True
..\..\..\..\StrongnameKey.snk
diff --git a/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md b/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md
index a5d9f7a8..436b3fc5 100644
--- a/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md
+++ b/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md
@@ -14,4 +14,5 @@ According to the docs it should be possible to create NuGet packages without nus
all stuff can be set in the csproj file. We still use nuspec because I cannot fix the following issues:
* Set the package icon in cssproj so that it is displayed.
-* We want a single package for all (e.g.) PDFsharp assemblies. Currently I don’t know how to manage that without a nuspec file.
+* We want a single package for all (e.g.) PDFsharp assemblies.
+ Currently I don’t know how to manage that without a nuspec file.
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt
index 03e61dbb..52986d6f 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt
@@ -1,5 +1,9 @@
-MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications.
+MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications.
This package relies on Windows Forms (GDI+) and can be used under Windows only.
See https://docs.pdfsharp.net for details.
+
+See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options.
+Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times.
+Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions.
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.Fewer.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.Fewer.nuspec
new file mode 100644
index 00000000..c6cde112
--- /dev/null
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.Fewer.nuspec
@@ -0,0 +1,46 @@
+
+
+
+ $id$
+ $version$
+ $description$
+ $summary$
+ $authors$
+ $owners$
+ $projectUrl$
+ MIT
+ images\MigraDoc-128x128.png
+ README.md
+ false
+ $releaseNotes$
+ $CopyrightMigraDoc$
+ $tags$
+
+ $title$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj
index b89b8c92..1314974d 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj
@@ -1,9 +1,9 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_NuGet_Window)
true
- true
+
false
True
..\..\..\..\StrongnameKey.snk
@@ -12,8 +12,9 @@
true
true
- $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec
PDFsharp-MigraDoc-GDI
+ $(NuGetId)-DEBUG
PDFsharp plus MigraDoc for Windows Forms apps
$(DebugBuildMessage)%0D$(NuGetDescription)
Creating Documents on the Fly.
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec
index 9a3b155a..1dc9c55f 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec
@@ -13,32 +13,40 @@
README.md
false
$releaseNotes$
- $copyright$
+ $CopyrightMigraDoc$
$tags$
$title$
-
-
+
+
-
+
-
-
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
+
+
+
@@ -47,8 +55,9 @@
-
+
+
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt
index 46a1582f..4616f1fe 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt
@@ -1,5 +1,5 @@
-This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher.
-The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’.
-The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’.
+This is a version of PDFsharp and MigraDoc compatible with .NET 8 and higher.
+The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’.
+The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’.
See the project docs site for further information:
-https://docs.pdfsharp.net/link/readme-v6.2.html
+https://docs.pdfsharp.net/link/readme-v7.0.html
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt
index a24bae03..92f0069a 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt
@@ -1,5 +1,9 @@
-MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications.
+MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications.
This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only.
See https://docs.pdfsharp.net for details.
+
+See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options.
+Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times.
+Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions.
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.Fewer.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.Fewer.nuspec
new file mode 100644
index 00000000..fbc5ba01
--- /dev/null
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.Fewer.nuspec
@@ -0,0 +1,46 @@
+
+
+
+ $id$
+ $version$
+ $description$
+ $summary$
+ $authors$
+ $owners$
+ $projectUrl$
+ MIT
+ images\MigraDoc-128x128.png
+ README.md
+ false
+ $releaseNotes$
+ $CopyrightMigraDoc$
+ $tags$
+
+ $title$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj
index 676107fe..7b4dae43 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj
@@ -1,9 +1,9 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_NuGet_Window)
true
- true
+
false
True
..\..\..\..\StrongnameKey.snk
@@ -12,8 +12,9 @@
true
true
- $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec
PDFsharp-MigraDoc-WPF
+ $(NuGetId)-DEBUG
PDFsharp plus MigraDoc for Windows WPF apps
$(DebugBuildMessage)%0D$(NuGetDescription)
Creating Documents on the Fly.
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec
index 7a5a15e7..01adbf30 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec
@@ -13,32 +13,40 @@
README.md
false
$releaseNotes$
- $copyright$
+ $CopyrightMigraDoc$
$tags$
$title$
-
-
+
+
-
+
-
-
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
+
+
+
@@ -47,8 +55,9 @@
-
+
+
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt
index 46a1582f..4616f1fe 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt
+++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt
@@ -1,5 +1,5 @@
-This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher.
-The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’.
-The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’.
+This is a version of PDFsharp and MigraDoc compatible with .NET 8 and higher.
+The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’.
+The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’.
See the project docs site for further information:
-https://docs.pdfsharp.net/link/readme-v6.2.html
+https://docs.pdfsharp.net/link/readme-v7.0.html
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt
index 0cc42ad4..99d2af4b 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt
+++ b/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt
@@ -1,5 +1,9 @@
-MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications.
+MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications.
This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS.
See https://docs.pdfsharp.net for details.
+
+See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options.
+Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times.
+Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions.
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.Fewer.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.Fewer.nuspec
new file mode 100644
index 00000000..3f08bda7
--- /dev/null
+++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.Fewer.nuspec
@@ -0,0 +1,45 @@
+
+
+
+ $id$
+ $version$
+ $description$
+ $summary$
+ $authors$
+ $owners$
+ $projectUrl$
+ MIT
+ images\MigraDoc-128x128.png
+ README.md
+ false
+ $releaseNotes$
+ $CopyrightMigraDoc$
+ $tags$
+
+ $title$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj
index 6103944a..b2a3762f 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj
+++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj
@@ -1,8 +1,8 @@
- net6.0;net8.0;netstandard2.0
- true
+ $(PDFsharpTargetFrameworks_NuGet)
+
false
True
..\..\..\..\StrongnameKey.snk
@@ -11,8 +11,9 @@
true
true
- $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec
PDFsharp-MigraDoc
+ $(NuGetId)-DEBUG
PDFsharp plus MigraDoc for Windows and Linux
$(DebugBuildMessage)%0D$(NuGetDescription)
Creating Documents on the Fly.
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec
index 3f2b5634..8972a69d 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec
+++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec
@@ -13,39 +13,47 @@
README.md
false
$releaseNotes$
- $copyright$
+ $CopyrightMigraDoc$
$tags$
$title$
-
-
+
+
-
+
-
-
+
+
+
+
+
+
+
-
+
-
+
-
+
-
-
+
+
+
+
-
+
+
diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt
index 46a1582f..4616f1fe 100644
--- a/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt
+++ b/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt
@@ -1,5 +1,5 @@
-This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher.
-The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’.
-The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’.
+This is a version of PDFsharp and MigraDoc compatible with .NET 8 and higher.
+The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’.
+The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’.
See the project docs site for further information:
-https://docs.pdfsharp.net/link/readme-v6.2.html
+https://docs.pdfsharp.net/link/readme-v7.0.html
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt
index 39e800a7..be622cff 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt
@@ -1,5 +1,9 @@
-PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents.
+PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents.
This package relies on Windows Forms (GDI+) and can be used under Windows only.
See https://docs.pdfsharp.net for details.
+
+See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options.
+Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times.
+Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions.
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.Fewer.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.Fewer.nuspec
new file mode 100644
index 00000000..c98d5d15
--- /dev/null
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.Fewer.nuspec
@@ -0,0 +1,44 @@
+
+
+
+ $id$
+ $version$
+ $description$
+ $summary$
+ $authors$
+ $owners$
+ $projectUrl$
+ MIT
+ images\PDFsharp-128x128.png
+ README.md
+ false
+ $releaseNotes$
+ $CopyrightPdfSharp$
+ $tags$
+
+ $title$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj
index 76da6886..85ec6c4e 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj
@@ -1,9 +1,9 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_NuGet_Window)
true
- true
+
false
True
..\..\..\..\StrongnameKey.snk
@@ -12,8 +12,9 @@
true
true
- $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec
PDFsharp-GDI
+ $(NuGetId)-DEBUG
PDFsharp for Windows Forms apps
$(DebugBuildMessage)%0D$(NuGetDescription)
A .NET library for processing PDF.
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec
index e44df4b4..734e4526 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec
@@ -13,29 +13,36 @@
README.md
false
$releaseNotes$
- $copyright$
+ $CopyrightPdfSharp$
$tags$
$title$
-
-
+
+
-
-
+
+
+
+
+
+
-
+
-
+
-
+
+
+
+
@@ -44,8 +51,9 @@
-
+
+
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt
index f5df80ae..f7d8d9c8 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt
@@ -1,6 +1,6 @@
-This is a version of PDFsharp compatible with .NET 6 and higher.
-The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’.
-The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’.
+This is a version of PDFsharp compatible with .NET 8 and higher.
+The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’.
+The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’.
We also publish packages that contain PDFsharp plus MigraDoc.
See the project docs site for further information:
-https://docs.pdfsharp.net/link/readme-v6.2.html
+https://docs.pdfsharp.net/link/readme-v7.0.html
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt
index 2627b98f..b82a01aa 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt
@@ -1,5 +1,9 @@
-PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents.
+PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents.
This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only.
See https://docs.pdfsharp.net for details.
+
+See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options.
+Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times.
+Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions.
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.Fewer.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.Fewer.nuspec
new file mode 100644
index 00000000..1eef4e64
--- /dev/null
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.Fewer.nuspec
@@ -0,0 +1,44 @@
+
+
+
+ $id$
+ $version$
+ $description$
+ $summary$
+ $authors$
+ $owners$
+ $projectUrl$
+ MIT
+ images\PDFsharp-128x128.png
+ README.md
+ false
+ $releaseNotes$
+ $CopyrightPdfSharp$
+ $tags$
+
+ $title$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj
index 66ca02ae..5928ff6c 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj
@@ -1,9 +1,9 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_NuGet_Window)
true
- true
+
false
True
..\..\..\..\StrongnameKey.snk
@@ -12,8 +12,9 @@
true
true
- $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec
PDFsharp-WPF
+ $(NuGetId)-DEBUG
PDFsharp for Windows WPF apps
$(DebugBuildMessage)%0D$(NuGetDescription)
A .NET library for processing PDF.
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec
index d930f34b..2094e3fb 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec
@@ -13,29 +13,36 @@
README.md
false
$releaseNotes$
- $copyright$
+ $CopyrightPdfSharp$
$tags$
$title$
-
-
+
+
-
-
+
+
+
+
+
+
-
+
-
+
-
+
+
+
+
@@ -44,8 +51,9 @@
-
+
+
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt
index f5df80ae..f7d8d9c8 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt
+++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt
@@ -1,6 +1,6 @@
-This is a version of PDFsharp compatible with .NET 6 and higher.
-The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’.
-The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’.
+This is a version of PDFsharp compatible with .NET 8 and higher.
+The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’.
+The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’.
We also publish packages that contain PDFsharp plus MigraDoc.
See the project docs site for further information:
-https://docs.pdfsharp.net/link/readme-v6.2.html
+https://docs.pdfsharp.net/link/readme-v7.0.html
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt
index b6b011a5..432a269f 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt
+++ b/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt
@@ -1,5 +1,9 @@
-PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents.
+PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents.
This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS.
See https://docs.pdfsharp.net for details.
+
+See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options.
+Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times.
+Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions.
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.Fewer.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.Fewer.nuspec
new file mode 100644
index 00000000..2b2ced3e
--- /dev/null
+++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.Fewer.nuspec
@@ -0,0 +1,50 @@
+
+
+
+ $id$
+ $version$
+ $description$
+ $summary$
+ $authors$
+ $owners$
+ $projectUrl$
+ MIT
+ images\PDFsharp-128x128.png
+ README.md
+ false
+ $releaseNotes$
+ $CopyrightMigraDoc$
+ $tags$
+
+ $title$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj
index 1139a742..73410a8b 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj
+++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj
@@ -1,8 +1,8 @@
- net6.0;net8.0;netstandard2.0
- true
+ $(PDFsharpTargetFrameworks_NuGet)
+
false
True
@@ -12,8 +12,9 @@
true
true
- $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec
PDFsharp
+ $(NuGetId)-DEBUG
PDFsharp for Windows and Linux
$(DebugBuildMessage)%0D$(NuGetDescription)
A .NET library for processing PDF.
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec
index 4c01c173..5abe25d7 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec
+++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec
@@ -13,36 +13,43 @@
README.md
false
$releaseNotes$
- $copyright$
+ $CopyrightPdfSharp$
$tags$
$title$
-
-
+
+
-
-
+
+
+
+
+
+
-
+
-
-
+
+
+
+
-
+
+
diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt
index f5df80ae..f7d8d9c8 100644
--- a/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt
+++ b/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt
@@ -1,6 +1,6 @@
-This is a version of PDFsharp compatible with .NET 6 and higher.
-The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’.
-The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’.
+This is a version of PDFsharp compatible with .NET 8 and higher.
+The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’.
+The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’.
We also publish packages that contain PDFsharp plus MigraDoc.
See the project docs site for further information:
-https://docs.pdfsharp.net/link/readme-v6.2.html
+https://docs.pdfsharp.net/link/readme-v7.0.html
diff --git a/src/foundation/nuget/src/README.md b/src/foundation/nuget/src/README.md
index 0d977820..7dd29443 100644
--- a/src/foundation/nuget/src/README.md
+++ b/src/foundation/nuget/src/README.md
@@ -4,6 +4,9 @@ This folder contains dummy C# projects for the generation of the PDFsharp and Mi
## Developer notes
+* Add dependencies for newly referenced NuGet packages to the nuspec files.
+ Also libraries, that are part of the shared framework, have to be added for all frameworks.
+ Otherwise, exceptions may occur at runtime when trying to load the respective library.
* The description and release notes are single text files read by MSBUILD and put to the nuspec files.
This is done because putting the text directly in XML is very cumbersome.
* The description and release notes should be worded such that it is not necessary to revise them on every new release.
diff --git a/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj b/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj
index 74954604..7d49c23c 100644
--- a/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj
+++ b/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ $(PDFsharpTargetFrameworks_Exe)
diff --git a/src/foundation/src/MigraDoc/features/MigraDoc.Features/Program.cs b/src/foundation/src/MigraDoc/features/MigraDoc.Features/Program.cs
index db9c6ae5..72b18d98 100644
--- a/src/foundation/src/MigraDoc/features/MigraDoc.Features/Program.cs
+++ b/src/foundation/src/MigraDoc/features/MigraDoc.Features/Program.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using System;
diff --git a/src/foundation/src/MigraDoc/src/Directory.Build.props b/src/foundation/src/MigraDoc/src/Directory.Build.props
index 7197970b..cf05c780 100644
--- a/src/foundation/src/MigraDoc/src/Directory.Build.props
+++ b/src/foundation/src/MigraDoc/src/Directory.Build.props
@@ -3,6 +3,7 @@
MigraDoc
+ $(CopyrightMigraDoc)
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlParser.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlParser.cs
index 00a983a7..faa8072b 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlParser.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlParser.cs
@@ -150,7 +150,7 @@ Styles ParseStyles(Styles styles)
{
// StyleName [: BaseStyleName]
// {
- // ...
+ // …
// }
Style? style = null;
try
@@ -276,7 +276,7 @@ EmbeddedFiles ParseEmbeddedFiles(EmbeddedFiles embeddedFiles)
ReadMoreContent:
// If a section contains only one paragraph, the paragraph keyword is omitted in MDDDL and the paragraph content is directly inserted into the section.
// The IsParagraphContent() check has to be made, before moving to the next token by ReadCode().
- if (IsParagraphContent())
+ if (IsParagraphContent())
{
// Paragraph content was inserted directly into the section.
var paragraph = section.Elements.AddParagraph();
@@ -430,12 +430,12 @@ DocumentElements ParseDocumentElements(DocumentElements elements, Symbol context
// All section content will be treated as paragraph content.
//
// but this is ambiguous:
- // \section { \image(...) }
+ // \section { \image(…) }
// It could be an image inside a paragraph or at the section level.
// In this case it will be treated as an image on section level.
//
// If this is not your intention it must be like this:
- // \section { \paragraph { \image(...) } }
+ // \section { \paragraph { \image(…) } }
//
while (TokenType == TokenType.KeyWord)
@@ -1254,9 +1254,9 @@ void ParseImage(Image image, bool paragraphContent)
{
// Future syntax by example
// \image("Name")
- // \image("Name")[...]
- // \image{base64...} // NYI
- // \image[...]{base64...} // NYI
+ // \image("Name")[…]
+ // \image{base64…} // NYI
+ // \image[…]{base64…} // NYI
Debug.Assert(image != null);
try
@@ -1920,33 +1920,30 @@ void ParseAttributeStatement(DocumentObject? doc)
ParagraphFormat paragraphFormat = (ParagraphFormat)doc;
TabStops tabStops = paragraphFormat.TabStops;
- if (true) // HACK_OLD in ParseAttributeStatement // BUG_OLD THHO4STLA Already existed in 2019.
- {
- bool fAddItem = Symbol == Symbol.PlusAssign;
- var tabStop = new TabStop();
+ bool fAddItem = Symbol == Symbol.PlusAssign;
+ var tabStop = new TabStop();
- ReadCode();
+ ReadCode();
- if (Symbol == Symbol.BraceLeft)
- {
- ParseAttributeBlock(tabStop);
- }
- else if (Symbol is Symbol.StringLiteral or Symbol.RealLiteral or Symbol.IntegerLiteral)
- {
- // Special hack for tab stops...
- Unit unit = Token;
- tabStop.SetValue("Position", unit);
-
- ReadCode();
- }
- else
- ThrowParserException(MdDomMsgs.UnexpectedSymbol(Token));
+ if (Symbol == Symbol.BraceLeft)
+ {
+ ParseAttributeBlock(tabStop);
+ }
+ else if (Symbol is Symbol.StringLiteral or Symbol.RealLiteral or Symbol.IntegerLiteral)
+ {
+ // Special hack for tab stops...
+ Unit unit = Token;
+ tabStop.SetValue("Position", unit);
- if (fAddItem)
- tabStops.AddTabStop(tabStop);
- else
- tabStops.RemoveTabStop(tabStop.Position);
+ ReadCode();
}
+ else
+ ThrowParserException(MdDomMsgs.UnexpectedSymbol(Token));
+
+ if (fAddItem)
+ tabStops.AddTabStop(tabStop);
+ else
+ tabStops.RemoveTabStop(tabStop.Position);
break;
case Symbol.BraceLeft:
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlScanner.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlScanner.cs
index d038f718..3a43d6c5 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlScanner.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlScanner.cs
@@ -311,7 +311,7 @@ internal Symbol PeekSymbol()
int idx = _idx - 1;
int length = _ddlLength - idx;
- // Move to first non whitespace
+ // Move to first non-white-space
char ch = Char.MinValue;
while (length > 0)
{
@@ -427,7 +427,7 @@ internal Symbol ReadText(bool rootLevel)
}
///
- /// Returns whether the linebreak should be ignored because the previous symbol is already a whitespace.
+ /// Returns whether the linebreak should be ignored because the previous symbol is already a white-space.
///
bool IgnoreLineBreak()
{
@@ -580,7 +580,7 @@ internal bool MoveToNextParagraphContentLine(bool rootLevel)
ScanNextChar();
while (loop)
{
- // Scan to next EOL and ignore any white space.
+ // Scan to next EOL and ignore any white-space.
MoveToNonWhiteSpaceOrEol();
switch (_currChar)
{
@@ -636,9 +636,9 @@ internal bool MoveToNextParagraphContentLine(bool rootLevel)
}
///
- /// If the current character is not a white space, the function immediately returns it.
- /// Otherwise, the DDL cursor is moved forward to the first non-white space or EOF.
- /// White spaces are SPACE, HT, VT, CR, and LF.???
+ /// If the current character is not a white-space, the function immediately returns it.
+ /// Otherwise, the DDL cursor is moved forward to the first non-white-space or EOF.
+ /// White-spaces are SPACE, HT, VT, CR, and LF.???
///
internal char MoveToNonWhiteSpaceOrEol()
{
@@ -660,9 +660,9 @@ internal char MoveToNonWhiteSpaceOrEol()
}
///
- /// If the current character is not a white space, the function immediately returns it.
- /// Otherwise, the DDL cursor is moved forward to the first non-white space or EOF.
- /// White spaces are SPACE, HT, VT, CR, and LF.
+ /// If the current character is not a white-space, the function immediately returns it.
+ /// Otherwise, the DDL cursor is moved forward to the first non-white-space or EOF.
+ /// White-spaces are SPACE, HT, VT, CR, and LF.
///
internal char MoveToNonWhiteSpace()
{
@@ -881,7 +881,7 @@ internal static bool IsOctDigit(char ch)
internal static bool IsLetter(char ch) => Char.IsLetter(ch);
///
- /// Is character a white space.
+ /// Is character a white-space.
///
internal static bool IsWhiteSpace(char ch) => Char.IsWhiteSpace(ch);
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/enums/TokenType.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/enums/TokenType.cs
index d63146cd..8d0f5c06 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/enums/TokenType.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/enums/TokenType.cs
@@ -9,7 +9,7 @@ namespace MigraDoc.DocumentObjectModel.IO
enum TokenType
{
///
- /// White space or comment.
+ /// White-space or comment.
///
None,
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/ErrorHelpers.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/ErrorHelpers.cs
index 606644a8..cac38922 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/ErrorHelpers.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/ErrorHelpers.cs
@@ -64,9 +64,9 @@ public static ArgumentException ArgumentException_UndefinedBaseStyle(string styl
//public static string InvalidEnumValue(T value) where T : unmanaged // struct, Enum
//{
- // // ... where T : enum
+ // // … where T : enum
// // -or-
- // // ... where T : Enum
+ // // … where T : Enum
// // is not implemented in C# because nobody has done this.
// // See Eric Lippert on this topic: http://stackoverflow.com/questions/1331739/enum-type-constraints-in-c-sharp
// // UPDATE: Enum constraint comes with C# 7.3, see: https://devblogs.microsoft.com/premier-developer/dissecting-new-generics-constraints-in-c-7-3/
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/LogMessages.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/LogMessages.cs
index 0fbdc31c..dd756dd3 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/LogMessages.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/LogMessages.cs
@@ -22,7 +22,7 @@ public static partial void ArgbValueIsConsideredEmptyColor(
// What to log?
- // Image not found (not loadable, not found, ...)
+ // Image not found (not loadable, not found, …)
// Font not found under Linux (use Linux substitution)
// Information/Trace-level for e.g. TabStop inheritance, style inheritance
// Differences in RTF vs Word (decimal tab)
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/Meta.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/Meta.cs
index 6f736716..b966d5b3 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/Meta.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/Meta.cs
@@ -97,7 +97,7 @@ public bool HasValue(string name)
{
// BUG_OLD: HasValue("a.b") not handled
if (name.Contains('.'))
- throw new NotImplementedException($"'{name}' contains a dot.");
+ throw new NotSupportedException($"'{name}' contains a dot.");
return ValueDescriptors.HasName(name);
}
@@ -109,7 +109,7 @@ public bool HasValue(string name)
public void SetNull(DocumentObject dom, string name)
{
if (name.Contains('.'))
- throw new NotImplementedException($"'{name}' contains a dot.");
+ throw new NotSupportedException($"'{name}' contains a dot.");
var vd = ValueDescriptors[name];
if (vd == null)
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Cells.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Cells.cs
index 12f03117..302ed6b7 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Cells.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Cells.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using System.Diagnostics.CodeAnalysis;
@@ -92,7 +92,7 @@ public Row Row
}
///
- /// Resizes these cells list if necessary.
+ /// Resizes these cells¹ list if necessary.
///
void Resize(int index)
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Column.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Column.cs
index 37653e7d..7d902a82 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Column.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Column.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using System.Diagnostics.CodeAnalysis;
@@ -118,7 +118,7 @@ public int Index
///
/// Gets a cell by its row index. The first cell has index 0.
///
- public Cell? this[int index] => Values.Index is not null ? Table.Rows[index][Values.Index.Value] : null; // BUG_OLD Doesnt use Index property to guarantee getter loop ran.
+ public Cell? this[int index] => Values.Index is not null ? Table.Rows[index][Values.Index.Value] : null; // BUG_OLD Doesn’t use Index property to guarantee getter loop ran.
///
/// Sets or gets the default style name for all cells of the column.
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs
index 22db1b49..fa72795e 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs
@@ -39,7 +39,7 @@ void Init(Table table)
#if true
int rows = table.Rows.Count;
int columns = table.Columns.Count;
- var flags = new Boolean[rows, columns];
+ var flags = new bool[rows, columns];
for (int rwIdx = 0; rwIdx < rows; ++rwIdx)
{
for (int clmIdx = 0; clmIdx < columns; ++clmIdx)
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/VisitorBase.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/VisitorBase.cs
index d4e8faa8..987686f3 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/VisitorBase.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/VisitorBase.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using MigraDoc.DocumentObjectModel.Tables;
@@ -422,7 +422,7 @@ static void RealizeOrientation(PageSetup pageSetup)
}
///
- /// Updates the orientation according to a PageSetups PageWidth and PageHeight.
+ /// Updates the orientation according to a PageSetup’s PageWidth and PageHeight.
///
static void UpdateOrientation(PageSetup pageSetup)
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Borders.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Borders.cs
index 9215eb31..8c3c4d61 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Borders.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Borders.cs
@@ -121,24 +121,6 @@ protected override object DeepCopy()
return borders;
}
- /////
- ///// Gets an enumerator for the borders object.
- /////
- //public IEnumerator GetEnumerator()
- //{
- // throw new NotImplementedException();
- // //var ht = new Dictionary
- // //{
- // // { "Top", Values.Top },
- // // { "Left", Values.Left },
- // // { "Bottom", Values.Bottom },
- // // { "Right", Values.Right },
- // // { "DiagonalUp", Values.DiagonalUp },
- // // { "DiagonalDown", Values.DiagonalDown }
- // //};
- // //return new BorderEnumerator(ht);
- //}
-
///
/// Clears all Border objects from the collection. Additionally, 'Borders = null'
/// is written to the DDL stream when serialized.
@@ -420,60 +402,6 @@ internal void Serialize(Serializer serializer, Borders? refBorders)
return null;
}
- // ///
- // /// Returns an enumerator that can iterate through the Borders.
- // ///
- // public class BorderEnumerator : IEnumerator
- // {
- //#war/ning This class must be checked with a unit test.
- // ///
- // /// Creates a new BorderEnumerator.
- // ///
- // public BorderEnumerator(Dictionary ht)
- // {
- // _ht = ht;
- // _index = -1;
- // }
-
- // public void Dispose()
- // => throw new NotImplementedException();
-
- // ///
- // /// Sets the enumerator to its initial position, which is before the first element in the border collection.
- // ///
- // public void Reset() => _index = -1;
-
- // object IEnumerator.Current => Current;
-
- // ///
- // /// Gets the current element in the border collection.
- // ///
- // public Border Current
- // {
- // get
- // {
- // IEnumerator enumerator = _ht.GetEnumerator();
- // enumerator.Reset();
- // for (int idx = 0; idx < _index + 1; idx++)
- // enumerator.MoveNext();
- // // return (((DictionaryEntry)enumerator.Current).Value as Border)!; // B_UG: May return null
- // return (((KeyValuePair)enumerator.Current).Value as Border)!;
- // }
- // }
-
- // ///
- // /// Advances the enumerator to the next element of the border collection.
- // ///
- // public bool MoveNext()
- // {
- // _index++;
- // return (_index < _ht.Count);
- // }
-
- // int _index;
- // readonly Dictionary _ht;
- // }
-
///
/// Returns the metaobject of this instance.
///
@@ -589,11 +517,5 @@ public Color? Color
///
public bool? BordersCleared { get; set; }
}
-
- //IEnumerator IEnumerable.GetEnumerator()
- //{
- // throw new NotImplementedException();
- // //return GetEnumerator();
- //}
}
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Character.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Character.cs
index 9ff8eb76..d64d7290 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Character.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Character.cs
@@ -171,7 +171,7 @@ internal override void Serialize(Serializer serializer)
{
if (SymbolName == SymbolName.Blank)
{
- // Note: Don’t try to optimize it by leaving away the braces in case a single space is added.
+ // Do not try to optimize it by leaving away the braces in case a single space is added.
// This would lead to confusion with '(' in directly following text.
text = Invariant($@"\\space({Count})");
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Color.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Color.cs
index 4d1a106f..936898de 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Color.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Color.cs
@@ -52,7 +52,7 @@ static Color()
// Aqua == Cyan
// Fuchsia == Magenta
//var key = new Style(); ???
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
StandardColors.TryAdd(d, c);
#else
if (!StandardColors.ContainsKey(d))
@@ -307,7 +307,7 @@ public static Color Parse(string color)
//
// While comparing a string to "" may seem like it’s checking for an empty string, it’s actually checking
// for a specific value of an empty string. This can be a problem if the empty string is represented by
- // something other than "" in the code, such as null or whitespace.
+ // something other than "" in the code, such as null or white-space.
//
// On the other hand, comparing the length of a string to 0 is always checking for an empty string,
// regardless of how it’s represented in the code. However, this operation can be less efficient than comparing
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/DdlEncoder.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/DdlEncoder.cs
index 806174bc..a841a237 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/DdlEncoder.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/DdlEncoder.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using System.Text;
@@ -22,7 +22,7 @@ public static class DdlEncoder
var sb = new StringBuilder(length + (length >> 2));
for (int index = 0; index < length; index++)
{
- // Dont convert characters into DDL.
+ // Don’t convert characters into DDL.
char ch = str[index];
switch (ch)
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Font.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Font.cs
index f417f857..b0cff74d 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Font.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Font.cs
@@ -302,17 +302,12 @@ internal void Serialize(Serializer serializer, Font? font)
else
fontStyle = "(\"" + ((FormattedText)Parent).Style + "\")";
- //bool needBlank = false; // nice, but later...
+ //bool needBlank = false; // nice, but later…
serializer.Write("\\font" + fontStyle + "[");
if (!String.IsNullOrEmpty(Values.Name))
serializer.WriteSimpleAttribute("Name", Name);
-#if DEBUG_ // Test
- if (Size != Unit.Empty && Size != 0 && Size.Point == 0)
- _ = typeof(int);
-#endif
-
if (!Values.Size.IsValueNullOrEmpty())
serializer.WriteSimpleAttribute("Size", Size);
@@ -348,11 +343,6 @@ internal void Serialize(Serializer serializer, Font? font)
serializer.WriteSimpleAttribute("Name", Name);
}
-#if DEBUG_
- // Test
- if (!_size.IsNull && Size != 0 && Size.Point == 0)
- _ = typeof(int);
-#endif
if (!Values.Size.IsValueNullOrEmpty() && (font == null || Size != font.Size))
serializer.WriteSimpleAttribute("Size", Size);
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/HeadersFooters.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/HeadersFooters.cs
index dad5ad08..5e8aef94 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/HeadersFooters.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/HeadersFooters.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using MigraDoc.DocumentObjectModel.Visitors;
@@ -136,15 +136,15 @@ internal override void Serialize(Serializer serializer)
bool hasEvenPage = HasHeaderFooter(Values.EvenPage);
bool hasFirstPage = HasHeaderFooter(Values.FirstPage);
- // \primary...
+ // \primary…
if (hasPrimary)
Primary.Serialize(serializer, "primary");
- // \even...
+ // \even…
if (hasEvenPage)
EvenPage.Serialize(serializer, "evenpage");
- // \firstpage...
+ // \firstpage…
if (hasFirstPage)
FirstPage.Serialize(serializer, "firstpage");
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/ImageHelper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/ImageHelper.cs
index 3a33a8e4..00889c7e 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/ImageHelper.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/ImageHelper.cs
@@ -53,7 +53,7 @@ public static bool InSubfolder(string root, string filename, string imagePath, s
///
public static string ExtractPageNumber(string path, out int pageNumber)
{
- // Note: duplicated from class XPdfForm.
+ // This code is duplicated from class XPdfForm.
if (path == null)
throw new ArgumentNullException(nameof(path));
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs
index a3ed8f10..b2e181d9 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs
@@ -86,7 +86,7 @@ void WriteStamp()
if (_fWriteStamp)
{
WriteComment("Created by empira MigraDoc Document Object Model");
- WriteComment(String.Format("generated file created {0:d} at {0:t}", DateTime.Now));
+ WriteComment(String.Format("generated file created {0:d} at {0:t}", DateTimeOffset.Now));
}
}
@@ -297,7 +297,7 @@ void WriteLineToStream(string text)
/////
///// Mighty function to figure out if a blank is required as separator.
- ///// // Does not work without context...
+ ///// // Does not work without context…
/////
//bool IsBlankRequired(char left, char right)
//{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Style.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Style.cs
index a45c9b3f..7bdc60f0 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Style.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Style.cs
@@ -141,7 +141,7 @@ public string BaseStyle
// Self assignment is allowed.
if (String.Compare(Values.BaseStyle, value, StringComparison.OrdinalIgnoreCase) == 0)
{
- Values.BaseStyle = value; // character case may change...
+ Values.BaseStyle = value; // character case may change…
return;
}
@@ -161,7 +161,7 @@ public string BaseStyle
throw new ArgumentException(msg);
}
- if (idxBaseStyle >= 0) // BUG_OLD THHO4STLA Was "idxBaseStyle > 1".
+ if (idxBaseStyle >= 0)
{
// styles cannot be null if idxBaseStyle >= 0.
Debug.Assert(styles != null, nameof(styles) + " != null");
@@ -232,7 +232,7 @@ public StyleType Type
if (Values.BaseStyle == "")
throw new ArgumentException("User-defined Style defined without a BaseStyle");
- // REVIEW KlPo4StLa Special treatment for DefaultParagraphFont faulty (DefaultParagraphFont not returned via styles["name"]).
+ // Special treatment for DefaultParagraphFont faulty (DefaultParagraphFont not returned via styles["name"]).
if (Values.BaseStyle == DefaultParagraphFontName)
return styles[0];
@@ -307,14 +307,14 @@ internal override void Serialize(Serializer serializer)
// case: built-in style with unmodified base style name
string name = DdlEncoder.QuoteIfNameContainsBlanks(Name);
serializer.WriteLineNoCommit(name);
- // It’s fine if we have the predefined base style, but...
- // ...the base style may have been modified or may even have a modified base style.
+ // It’s fine if we have the predefined base style, but…
+ // … the base style may have been modified or may even have a modified base style.
// Methinks it’s wrong to compare with the built-in style, so let’s compare with the
// real base style:
refStyle = Document.Styles[Document.Styles.GetIndex(Values.BaseStyle!)]; // BUG_OLD: Base style can be null
refFormat = refStyle.ParagraphFormat;
//refFont = refFormat.Font;
- // Note: we must write "Underline = none" if the base style has "Underline = single" - we cannot
+ // We must write "Underline = none" if the base style has "Underline = single" - we cannot
// detect this if we compare with the built-in style that has no underline.
// Known problem: Default values like "OutlineLevel = Level1" will now be serialized.
// TODO_OLD: Optimize DDL output, remove redundant default values.
@@ -366,7 +366,7 @@ internal override void Serialize(Serializer serializer)
///
void Optimize()
{
- // Just here as a reminder to do it...
+ // Just here as a reminder to do it…
}
///
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Unit.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Unit.cs
index 4c3bc80c..3392cb41 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Unit.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Unit.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
namespace MigraDoc.DocumentObjectModel
@@ -58,7 +58,7 @@ void INullableValue.SetValue(object? value)
///
/// Resets this instance,
- /// i.e. IsNull() will return true afterwards.
+ /// i.e. IsNull() will return true afterward.
///
void INullableValue.SetNull()
{
@@ -443,8 +443,8 @@ public static implicit operator Unit(string? value)
var unit = Zero;
value = value.Trim();
- // For Germans...
- value = value.Replace(',', '.');
+ // For Germans…
+ value = value.Replace(',', '.'); // TODO: Remove, but this is a breaking change.
int count = value.Length;
int valLen = 0;
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/enums/SymbolName.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/enums/SymbolName.cs
index a75ccbce..0d37e03f 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/enums/SymbolName.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/enums/SymbolName.cs
@@ -8,7 +8,7 @@ namespace MigraDoc.DocumentObjectModel
///
public enum SymbolName : uint
{
- // ===== \space(...) =====
+ // ===== \space(…) =====
///
/// A regular blank.
///
@@ -52,7 +52,7 @@ public enum SymbolName : uint
ParaBreak = 0xF4000007,
//MarginBreak = 0xF4000002,
- // ===== \symbol(...) =====
+ // ===== \symbol(…) =====
///
/// The Euro symbol €.
///
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj
index b97949f3..48eb21e9 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj
@@ -1,19 +1,20 @@
- net6.0;net8.0;net462;netstandard2.0
+ $(PDFsharpTargetFrameworks_Library)
MigraDoc
True
..\..\..\..\..\StrongnameKey.snk
+ true
true
-
- ;
-
+
0
@@ -32,7 +33,8 @@
-
+
+
@@ -41,6 +43,6 @@
-
+
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs
index cddf77a9..4cc1871b 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs
@@ -3,7 +3,9 @@
using PdfSharp.Internal;
+#pragma warning disable IDE0130 // Namespace does not match folder structure
namespace MigraDoc
+#pragma warning restore IDE0130 // Namespace does not match folder structure
{
///
/// Version information for all MigraDoc related assemblies.
@@ -11,7 +13,6 @@ namespace MigraDoc
// TODO_OLD: These literals are not completely in sync with the NuGet metadata. Should be reviewed and fixed.
public static class MigraDocProductVersionInformation
{
- // Cannot use const string anymore because GitVersionInformation used static string.
// The fields are reordered to take initialization chronology into account.
///
@@ -27,43 +28,48 @@ public static class MigraDocProductVersionInformation
///
/// The major version number of the product.
///
- public static readonly string VersionMajor = PdfSharpGitVersionInformation.Major;
+ public static readonly string VersionMajor = SemVersionInformation.Major;
///
/// The minor version number of the product.
///
- public static readonly string VersionMinor = PdfSharpGitVersionInformation.Minor;
+ public static readonly string VersionMinor = SemVersionInformation.Minor;
///
/// The patch number of the product.
///
- public static readonly string VersionPatch = PdfSharpGitVersionInformation.Patch;
+ public static readonly string VersionPatch = SemVersionInformation.Patch;
///
/// The Version pre-release string for NuGet.
///
- public static readonly string VersionPreRelease = PdfSharpGitVersionInformation.PreReleaseLabel;
+ public static readonly string VersionPreRelease = SemVersionInformation.PreReleaseLabel;
///
/// The PDF creator application information string.
/// The PDF producer (created by) is PDFsharp anyway.
///
- public static readonly string Creator = $"{Title} {PdfSharpGitVersionInformation.InformationalVersion} ({Url})";
+ public static readonly string Creator = $"{Title} {SemVersionInformation.InformationalVersion} ({Url})";
///
/// The full version number.
///
- public static readonly string Version = PdfSharpGitVersionInformation.MajorMinorPatch;
+ public static readonly string Version = SemVersionInformation.Version;
///
- /// The full semantic version number created by GitVersion.
+ /// The full file version number.
///
- public static readonly string SemanticVersion = PdfSharpGitVersionInformation.SemVer;
+ public static readonly string FileVersion = SemVersionInformation.FileVersion;
+
+ ///
+ /// The full semantic version number.
+ ///
+ public static readonly string SemanticVersion = SemVersionInformation.InformationalVersion;
///
/// The home page of this product.
///
- public const string Url = "www.pdfsharp.net";
+ public const string Url = "www.pdfsharp.com";
///
/// Unused.
@@ -83,7 +89,7 @@ public static class MigraDocProductVersionInformation
///
/// The copyright information.
///
- public const string Copyright = "Copyright © 2001-2025 empira Software GmbH."; // Also used as NuGet Copyright.
+ public const string Copyright = "Copyright © 2001-2026 empira Software GmbH."; // Not used as NuGet Copyright. See Directory.Build.Props.
///
/// The trademark of the product.
@@ -96,12 +102,6 @@ public static class MigraDocProductVersionInformation
public const string Culture = "";
// Build = days since 2001-07-04 - change values ONLY here
-#if DEBUG_
- // ReSharper disable RedundantNameQualifier
- //public static int BuildNumber = (System.DateTime.Now - new System.DateTime(2001, 7, 4)).Days;
- public static int BuildNumber = (System.DateTime.Now - new System.DateTime(2005, 1, 1)).Days; // Same BuildNumber like PDFsharp
- // ReSharper restore RedundantNameQualifier
-#endif
#if Not_used_anymore // #DELETE 25-12-31
@@ -171,7 +171,7 @@ public static class MigraDocProductVersionInformation
public const string NuGetLicenseUrl = "http://www.pdfsharp.net/MigraDoc_License.ashx";
///
- /// Nuspec Doc: A Boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed.
+ /// Nuspec Doc: A boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed.
///
public const bool NuGetRequireLicenseAcceptance = false;
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md
index 6c1d18fe..677ae36e 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md
@@ -3,5 +3,5 @@
One single build with all target frameworks:
```xml
-net6.0;net8.0;net462;netstandard2.0
+net8.0;net462;netstandard2.0
```
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj
index ba85e789..71105ccd 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj
@@ -1,14 +1,16 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
MigraDoc
+ $(DefineConstants);GDI
true
- GDI
+
True
..\..\..\..\..\StrongnameKey.snk
+ true
@@ -24,13 +26,14 @@
-
+
+
@@ -108,209 +111,10 @@
-
-
-
+
+
+
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs
index 7356ef0e..4ebff5bd 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs
@@ -1,34 +1,6 @@
-#region MigraDoc - Creating Documents on the Fly
-//
-// Authors:
-// Stefan Lange
-//
-// Copyright (c) 2001-2019 empira Software GmbH, Cologne Area (Germany)
-//
-// http://www.pdfsharp.com
-// http://www.migradoc.com
-// http://sourceforge.net/projects/pdfsharp
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the "Software"),
-// to deal in the Software without restriction, including without limitation
-// the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and/or sell copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-// DEALINGS IN THE SOFTWARE.
-#endregion
-
-using System;
+// MigraDoc - Creating Documents on the Fly
+// See the LICENSE file in the solution root for more information.
+
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
@@ -46,9 +18,6 @@ namespace MigraDoc.Rendering.Forms
///
public class DocumentPreview : UserControl
{
- private PdfSharp.Forms.PagePreview _preview;
- private readonly Container _components = null!;
-
///
/// Initializes a new instance of the class.
///
@@ -74,11 +43,11 @@ protected override void Dispose(bool disposing)
Zoom GetNewZoomFactor(int currentZoom, bool larger)
{
- int[] values = new int[]
- {
- 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200,
- 250, 300, 350, 400, 450, 500, 600, 700, 800
- };
+ int[] values =
+ [
+ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200,
+ 250, 300, 350, 400, 450, 500, 600, 700, 800
+ ];
if (currentZoom <= 10 && !larger)
return Zoom.Percent10;
@@ -138,6 +107,7 @@ Zoom GetNewZoomFactor(int currentZoom, bool larger)
///
/// Gets or sets a DDL string or file.
///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string Ddl
{
get { return _ddl; }
@@ -152,6 +122,7 @@ public string Ddl
///
/// Gets or sets the current page.
///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Page
{
get { return _page; }
@@ -297,6 +268,7 @@ void DdlUpdated()
///
/// Gets or sets the MigraDoc document that is previewed in this control.
///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public MigraDoc.DocumentObjectModel.Document Document
{
get { return _document; }
@@ -403,6 +375,7 @@ public void MakeLarger()
///
/// The color of the page.
[Description("The background color of the page."), Category("Preview Properties")]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Color PageColor
{
get { return _preview.PageColor; }
@@ -415,6 +388,7 @@ public Color PageColor
///
/// The color of the desktop.
[Description("The color of the desktop."), Category("Preview Properties")]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Color DesktopColor
{
get { return _preview.DesktopColor; }
@@ -450,6 +424,7 @@ public bool ShowPage
/// Gets or sets the page size in point.
///
[Description("Determines the size (in points) of the page."), Category("Preview Properties")]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Size PageSize
{
get { return new Size((int)_preview.PageSize.Width, (int)_preview.PageSize.Height); }
@@ -534,9 +509,12 @@ protected override void OnMouseWheel(MouseEventArgs e)
NextPage();
}
- private void PreviewZoomChanged(object? sender, EventArgs e)
+ void PreviewZoomChanged(object? sender, EventArgs e)
{
OnZoomChanged(e);
}
+
+ PdfSharp.Forms.PagePreview _preview;
+ readonly Container _components = null!;
}
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs
index 3c8ce68d..9cdd63e0 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs
@@ -1,32 +1,5 @@
-#region MigraDoc - Creating Documents on the Fly
-//
-// Authors:
-// Stefan Lange
-//
-// Copyright (c) 2001-2019 empira Software GmbH, Cologne Area (Germany)
-//
-// http://www.pdfsharp.com
-// http://www.migradoc.com
-// http://sourceforge.net/projects/pdfsharp
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the "Software"),
-// to deal in the Software without restriction, including without limitation
-// the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and/or sell copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-// DEALINGS IN THE SOFTWARE.
-#endregion
+// MigraDoc - Creating Documents on the Fly
+// See the LICENSE file in the solution root for more information.
namespace MigraDoc.Rendering.Forms
{
@@ -96,7 +69,7 @@ public enum Zoom
///
/// The smallest possible zoom factor.
///
- Mininum = PdfSharp.Forms.Zoom.Mininum,
+ Minimum = PdfSharp.Forms.Zoom.Minimum,
///
/// The largest possible zoom factor.
///
@@ -163,7 +136,7 @@ public enum Zoom
///
/// The smallest possible zoom factor.
///
- Mininum = PdfSharp.Windows.Zoom.Mininum,
+ Minimum = PdfSharp.Windows.Zoom.Minimum,
///
/// The largest possible zoom factor.
///
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj
index 0f9d85e1..420f2ede 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj
@@ -1,18 +1,19 @@
- net6.0-windows;net8.0-windows;net462
- true
- MigraDoc
- true
-
- WPF
- True
- ..\..\..\..\..\StrongnameKey.snk
+ $(PDFsharpTargetFrameworks_Windows)
+ true
+ MigraDoc
+ $(DefineConstants);WPF
+ true
+
+
+ True
+ ..\..\..\..\..\StrongnameKey.snk
- true
+ true
@@ -24,11 +25,13 @@
-
+
+
+
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj
index c0b70dbf..4e37c636 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj
@@ -1,7 +1,7 @@
- net6.0;net8.0;netstandard2.0
+ $(PDFsharpTargetFrameworks_Library)
MigraDoc
True
..\..\..\..\..\StrongnameKey.snk
@@ -40,7 +40,8 @@
-
+
+
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GlobalDeclarations.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GlobalDeclarations.cs
index 2d7824e7..7e417fdf 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GlobalDeclarations.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GlobalDeclarations.cs
@@ -2,9 +2,11 @@
// See the LICENSE file in the solution root for more information.
global using System.IO;
+global using System.Diagnostics;
global using PdfSharp.Diagnostics;
global using static System.FormattableString;
+#if PSGFX
+global using PdfSharp.Graphics.XGfx;
+#endif
+//[assembly: ComVisible(false)]
-using System.Runtime.InteropServices;
-
-[assembly: ComVisible(false)]
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GraphicsDeclarations.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GraphicsDeclarations.cs
new file mode 100644
index 00000000..69889197
--- /dev/null
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GraphicsDeclarations.cs
@@ -0,0 +1,57 @@
+// MigraDoc - Creating Documents on the Fly
+// See the LICENSE file in the solution root for more information.
+
+#pragma warning disable CS1591
+
+#if PSGFX
+global using float_ = float;
+global using FLOAT_ = float;
+global using XPoint = PdfSharp.Graphics.Point;
+global using XRect = PdfSharp.Graphics.Rect;
+global using XSize = PdfSharp.Graphics.Size;
+global using XMatrix = PdfSharp.Graphics.Media.Matrix;
+global using XColor = PdfSharp.Graphics.Color;
+global using XColors = PdfSharp.Graphics.Colors;
+global using XPen = PdfSharp.Graphics.Media.Pen;
+global using XPens = PdfSharp.Graphics.Media.Pens;
+global using XLineJoin = PdfSharp.Graphics.Media.PenLineJoin;
+global using XDashStyle = PdfSharp.Graphics.Media.DashStyle;
+global using XDashStyles = PdfSharp.Graphics.Media.DashStyles;
+global using XBrush = PdfSharp.Graphics.Media.Brush;
+global using XBrushes = PdfSharp.Graphics.Media.Brushes;
+global using XSolidBrush = PdfSharp.Graphics.Media.SolidColorBrush;
+global using XLinearGradientBrush = PdfSharp.Graphics.Media.SolidColorBrush;
+global using XUnit = PdfSharp.Graphics.Unit;
+global using XUnitPt = PdfSharp.Graphics.UnitPt;
+global using XGraphicsState = int;
+global using XGraphics = PdfSharp.Graphics.Media.DrawingContext;
+//global using XFont = PdfSharp.Graphics.Text.FontFace;
+global using XFont = PdfSharp.Graphics.XGfx.XFontGfx;
+//global using XFontStyleEx = PdfSharp.Graphics.XGfx.XFontStyleEx;
+#else
+global using float_ = double;
+global using FLOAT_ = double;
+using System.Runtime.CompilerServices;
+#endif
+
+using System.Runtime.InteropServices;
+using PdfSharp.Drawing;
+
+#if PSGFX
+using PdfSharp.Graphics;
+using PdfSharp.Pdf;
+using PdfSharp.Fonts;
+#else
+public static class IdentityExtensions
+{
+ extension(XRect rect)
+ {
+ public PdfSharp.Drawing.XRect AsXRect => rect;
+ }
+
+ extension(XPoint point)
+ {
+ public PdfSharp.Drawing.XPoint AsXPoint => point;
+ }
+}
+#endif
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/ChartMapper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/ChartMapper.cs
index ab85bf45..897c8d1f 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/ChartMapper.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/ChartMapper.cs
@@ -17,8 +17,8 @@ ChartFrame MapObject(DocumentObjectModel.Shapes.Charts.Chart domChart)
{
var chartFrame = new ChartFrame
{
- Size = new XSize(domChart.Width.Point, domChart.Height.Point),
- Location = new XPoint(domChart.Left.Position.Point, domChart.Top.Position.Point)
+ Size = new XSize((float_)domChart.Width.Point, (float_)domChart.Height.Point),
+ Location = new XPoint((float_)domChart.Left.Position.Point, (float_)domChart.Top.Position.Point)
};
var chart = new Chart((ChartType)domChart.Type);
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FillFormatMapper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FillFormatMapper.cs
index 54cae6df..d42208d1 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FillFormatMapper.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FillFormatMapper.cs
@@ -12,7 +12,13 @@ class FillFormatMapper
void MapObject(FillFormat fillFormat, DocumentObjectModel.Shapes.FillFormat domFillFormat)
{
if (domFillFormat.Color.IsEmpty)
+ {
+#if PSGFX
+ fillFormat.Color = PdfSharp.Graphics.Color.FromArgb(0, 0, 0, 0);
+#else
fillFormat.Color = XColor.Empty;
+#endif
+ }
else
{
#if noCMYK
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FontMapper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FontMapper.cs
index fe62295b..3c876973 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FontMapper.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FontMapper.cs
@@ -16,8 +16,15 @@ void MapObject(Font font, DocumentObjectModel.Font domFont)
{
font.Bold = domFont.Bold;
if (domFont.Color.IsEmpty)
- font.Color = XColor.Empty;
+ {
+#if PSGFX
+ font.Color = new XColor();
+#else
+ font.Color = PdfSharp.Drawing.XColor.Empty;
+#endif
+ }
else
+
{
#if noCMYK
font.Color = XColor.FromArgb((int)domFont.Color.Argb);
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/LineFormatMapper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/LineFormatMapper.cs
index 944aebe0..6edddafa 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/LineFormatMapper.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/LineFormatMapper.cs
@@ -29,12 +29,21 @@ void MapObject(LineFormat lineFormat, DocumentObjectModel.Shapes.LineFormat domL
lineFormat.DashStyle = domLineFormat.DashStyle switch
{
+#if PSGFX
+ DocumentObjectModel.Shapes.DashStyle.Dash => XDashStyles.Dash,
+ DocumentObjectModel.Shapes.DashStyle.DashDot => XDashStyles.DashDot,
+ DocumentObjectModel.Shapes.DashStyle.DashDotDot => XDashStyles.DashDotDot,
+ DocumentObjectModel.Shapes.DashStyle.Solid => XDashStyles.Solid,
+ DocumentObjectModel.Shapes.DashStyle.SquareDot => XDashStyles.Dot,
+ _ => XDashStyles.Solid
+#else
DocumentObjectModel.Shapes.DashStyle.Dash => XDashStyle.Dash,
DocumentObjectModel.Shapes.DashStyle.DashDot => XDashStyle.DashDot,
DocumentObjectModel.Shapes.DashStyle.DashDotDot => XDashStyle.DashDotDot,
DocumentObjectModel.Shapes.DashStyle.Solid => XDashStyle.Solid,
DocumentObjectModel.Shapes.DashStyle.SquareDot => XDashStyle.Dot,
_ => XDashStyle.Solid
+#endif
};
lineFormat.Style = domLineFormat.Style switch
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.Extensions/UnitConversions.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.Extensions/UnitConversions.cs
index 8f7d3284..25ca61a7 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.Extensions/UnitConversions.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.Extensions/UnitConversions.cs
@@ -3,6 +3,10 @@
using MigraDoc.DocumentObjectModel;
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics;
+#endif
+using Unit = MigraDoc.DocumentObjectModel.Unit;
namespace MigraDoc.Rendering.Extensions
{
@@ -18,9 +22,14 @@ public static XUnit ToXUnit(this Unit unit)
{
var xGraphicsUnit = unit.Type.TryGetAsXGraphicsUnit();
if (xGraphicsUnit != null)
- return new XUnit(unit.Value, xGraphicsUnit.Value);
-
- return XUnit.FromPoint(unit.Point);
+ {
+#if PSGFX
+ return new XUnit((float_)unit.Value, (GraphicsUnit)xGraphicsUnit.Value);
+#else
+ return new XUnit((float_)unit.Value, xGraphicsUnit.Value);
+#endif
+ }
+ return XUnit.FromPoint((float_)unit.Point);
}
static XGraphicsUnit? TryGetAsXGraphicsUnit(this UnitType unitType)
@@ -41,10 +50,11 @@ public static XUnit ToXUnit(this Unit unit)
///
public static Unit ToUnit(this XUnit xUnit)
{
+#if !PSGFX
var unitType = xUnit.Type.TryGetAsUnitType();
if (unitType != null)
return new Unit(xUnit.Value, unitType.Value);
-
+#endif
return Unit.FromPoint(xUnit.Point);
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestParagraphIterator.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestParagraphIterator.cs
index fe4c6bfd..5c41f00d 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestParagraphIterator.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestParagraphIterator.cs
@@ -3,7 +3,7 @@
using MigraDoc.DocumentObjectModel;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
namespace MigraDoc.Rendering.UnitTest
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestTable.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestTable.cs
index 0023c2f2..70167e27 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestTable.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestTable.cs
@@ -4,7 +4,7 @@
using MigraDoc.DocumentObjectModel;
using MigraDoc.DocumentObjectModel.Tables;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
namespace MigraDoc.Rendering.UnitTest
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/BordersRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/BordersRenderer.cs
index 2827e20f..9af022c0 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/BordersRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/BordersRenderer.cs
@@ -1,10 +1,11 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using System.Diagnostics;
using MigraDoc.DocumentObjectModel;
using MigraDoc.DocumentObjectModel.Tables;
+#if !PSXGRA
using PdfSharp.Drawing;
+#endif
namespace MigraDoc.Rendering
{
@@ -80,7 +81,7 @@ internal XUnitPt GetWidth(BorderType type)
//if (!border._width.IsNull)
//if (border.Values.Width is not null)
if (!values.Width.IsValueNullOrEmpty())
- return values.Width!.Value.Point;
+ return (float_)values.Width!.Value.Point;
//if (!border._color.IsNull || !border._style.IsNull || border.Visible)
//if (border.Values.Color is not null || border.Values.Style is not null || border.Visible)
@@ -89,9 +90,9 @@ internal XUnitPt GetWidth(BorderType type)
//if (!_borders._width.IsNull)
//if (_borders.Values.Width is not null)
if (!_borders.Values.Width.IsValueNullOrEmpty())
- return _borders.Values.Width!.Value.Point;
+ return (float_)(_borders.Values.Width!.Value.Point);
- return 0.5;
+ return 0.5f;
}
}
else if (!(type == BorderType.DiagonalDown || type == BorderType.DiagonalUp))
@@ -104,12 +105,12 @@ internal XUnitPt GetWidth(BorderType type)
//if (!_borders._width.IsNull)
//if (_borders.Values.Width is not null)
if (!values.Width.IsValueNullOrEmpty())
- return values.Width!.Value.Point;
+ return (float_)(values.Width!.Value.Point);
//if (!_borders._color.IsNull || !_borders._style.IsNull || _borders.Visible)
//if (_borders.Values.Color is not null || _borders.Values.Style is not null || _borders.Visible)
if (!values.Color.IsValueNullOrEmpty() || values.Style is not null || _borders.Visible)
- return 0.5;
+ return 0.5f;
}
return 0;
}
@@ -130,7 +131,11 @@ internal void RenderVertically(BorderType type, XUnitPt left, XUnitPt top, XUnit
left += borderWidth / 2;
var pen = GetPen(type);
if (pen != null)
+#if PSGFX
+ _gfx.DrawLine(pen, new(left, top + height), new(left, top));
+#else
_gfx.DrawLine(pen, left, top + height, left, top);
+#endif
}
///
@@ -149,7 +154,11 @@ internal void RenderHorizontally(BorderType type, XUnitPt left, XUnitPt top, XUn
top += borderWidth / 2;
var pen = GetPen(type);
if (pen != null)
+#if PSGFX
+ _gfx.DrawLine(pen, new(left + width, top), new(left, top));
+#else
_gfx.DrawLine(pen, left + width, top, left, top);
+#endif
}
internal void RenderDiagonally(BorderType type, XUnitPt left, XUnitPt top, XUnitPt width, XUnitPt height)
@@ -158,6 +167,25 @@ internal void RenderDiagonally(BorderType type, XUnitPt left, XUnitPt top, XUnit
if (borderWidth == 0)
return;
+#if PSGFX
+ //XGraphicsState state = _gfx.Save();
+ //_gfx.IntersectClip(new XRect(left, top, width, height));
+
+ //if (type == BorderType.DiagonalDown)
+ //{
+ // var pen = GetPen(type);
+ // if (pen != null)
+ // _gfx.DrawLine(pen, left, top, left + width, top + height);
+ //}
+ //else if (type == BorderType.DiagonalUp)
+ //{
+ // var pen = GetPen(type);
+ // if (pen != null)
+ // _gfx.DrawLine(pen, left, top + height, left + width, top);
+ //}
+
+ //_gfx.Restore(state);
+#else
XGraphicsState state = _gfx.Save();
_gfx.IntersectClip(new XRect(left, top, width, height));
@@ -175,6 +203,7 @@ internal void RenderDiagonally(BorderType type, XUnitPt left, XUnitPt top, XUnit
}
_gfx.Restore(state);
+#endif
}
internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, XUnitPt width, XUnitPt height)
@@ -203,6 +232,20 @@ internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, X
switch (roundedCorner)
{
+#if PSGFX
+ case RoundedCorner.TopLeft:
+ //_gfx.DrawArc(borderPen, new XRect(x, y, ellipseWidth, ellipseHeight), 180, 90);
+ break;
+ case RoundedCorner.TopRight:
+ //_gfx.DrawArc(borderPen, new XRect(x - width, y, ellipseWidth, ellipseHeight), 270, 90);
+ break;
+ case RoundedCorner.BottomRight:
+ //_gfx.DrawArc(borderPen, new XRect(x - width, y - height, ellipseWidth, ellipseHeight), 0, 90);
+ break;
+ case RoundedCorner.BottomLeft:
+ //_gfx.DrawArc(borderPen, new XRect(x, y - height, ellipseWidth, ellipseHeight), 90, 90);
+ break;
+#else
case RoundedCorner.TopLeft:
_gfx.DrawArc(borderPen, new XRect(x, y, ellipseWidth, ellipseHeight), 180, 90);
break;
@@ -215,6 +258,7 @@ internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, X
case RoundedCorner.BottomLeft:
_gfx.DrawArc(borderPen, new XRect(x, y - height, ellipseWidth, ellipseHeight), 90, 90);
break;
+#endif
}
}
@@ -228,6 +272,32 @@ internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, X
var style = GetStyle(type);
switch (style)
{
+#if PSGFX
+ case BorderStyle.DashDot:
+ //pen.DashStyle = XDashStyle.DashDot;
+ break;
+
+ case BorderStyle.DashDotDot:
+ //pen.DashStyle = XDashStyle.DashDotDot;
+ break;
+
+ case BorderStyle.DashLargeGap:
+ //pen.DashPattern = [3, 3];
+ break;
+
+ case BorderStyle.DashSmallGap:
+ //pen.DashPattern = [5, 1];
+ break;
+
+ case BorderStyle.Dot:
+ //pen.DashStyle = XDashStyle.Dot;
+ break;
+
+ case BorderStyle.Single:
+ default:
+ //pen.DashStyle = XDashStyle.Solid;
+ break;
+#else
case BorderStyle.DashDot:
pen.DashStyle = XDashStyle.DashDot;
break;
@@ -252,6 +322,7 @@ internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, X
default:
pen.DashStyle = XDashStyle.Solid;
break;
+#endif
}
return pen;
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ChartRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ChartRenderer.cs
index 3047caed..ceb23069 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ChartRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ChartRenderer.cs
@@ -46,7 +46,7 @@ internal ChartRenderer(XGraphics gfx, RenderInfo renderInfo, FieldInfos? fieldIn
FormattedTextArea? GetFormattedTextArea(TextArea? area)
{
- return GetFormattedTextArea(area, Double.NaN);
+ return GetFormattedTextArea(area, FLOAT_.NaN);
}
void GetLeftRightVerticalPosition(out XUnitPt top, out XUnitPt bottom)
@@ -185,10 +185,10 @@ internal override void Format(Area area, FormatInfo? previousFormatInfo)
var formatInfo = (ChartFormatInfo)_renderInfo.FormatInfo;
var textArea = _chart.Values.HeaderArea;
- formatInfo.FormattedHeader = GetFormattedTextArea(textArea, _chart.Width.Point);
+ formatInfo.FormattedHeader = GetFormattedTextArea(textArea, (float)_chart.Width.Point);
textArea = _chart.Values.FooterArea;
- formatInfo.FormattedFooter = GetFormattedTextArea(textArea, _chart.Width.Point);
+ formatInfo.FormattedFooter = GetFormattedTextArea(textArea, (float)_chart.Width.Point);
textArea = _chart.Values.LeftArea;
formatInfo.FormattedLeft = GetFormattedTextArea(textArea);
@@ -232,7 +232,7 @@ XUnitPt AlignVertically(VerticalAlignment vAlign, XUnitPt top, XUnitPt bottom, X
XUnitPt GetTopBottomWidth()
{
ChartFormatInfo formatInfo = (ChartFormatInfo)_renderInfo.FormatInfo;
- XUnitPt width = _chart.Width.Point;
+ XUnitPt width = (float)_chart.Width.Point;
if (formatInfo.FormattedRight != null)
width -= formatInfo.FormattedRight.InnerWidth;
if (formatInfo.FormattedLeft != null)
@@ -270,13 +270,13 @@ void RenderArea(FormattedTextArea? area, Rectangle rect)
fillFormatRenderer.Render(rect.X, rect.Y, rect.Width, rect.Height);
XUnitPt top = rect.Y;
- top += textArea.TopPadding.Point;
+ top += (float)textArea.TopPadding.Point;
XUnitPt bottom = rect.Y + rect.Height;
- bottom -= textArea.BottomPadding.Point;
+ bottom -= (float)textArea.BottomPadding.Point;
top = AlignVertically(textArea.VerticalAlignment, top, bottom, area.ContentHeight);
XUnitPt left = rect.X;
- left += textArea.LeftPadding.Point;
+ left += (float)textArea.LeftPadding.Point;
RenderInfo[] renderInfos = area.GetRenderInfos();
RenderByInfos(left, top, renderInfos);
@@ -321,16 +321,16 @@ void RenderPlotArea(PlotArea area, Rectangle rect)
var chartFrame = ((ChartFormatInfo)_renderInfo.FormatInfo).ChartFrame ?? NRT.ThrowOnNull("BUG_OLD");
XUnitPt top = rect.Y;
- top += area.TopPadding.Point;
+ top += (float)area.TopPadding.Point;
XUnitPt bottom = rect.Y + rect.Height;
- bottom -= area.BottomPadding.Point;
+ bottom -= (float)area.BottomPadding.Point;
XUnitPt left = rect.X;
- left += area.LeftPadding.Point;
+ left += (float)area.LeftPadding.Point;
XUnitPt right = rect.X + rect.Width;
- right -= area.RightPadding.Point;
+ right -= (float)area.RightPadding.Point;
chartFrame.Location = new XPoint(left, top);
chartFrame.Size = new XSize(right - left, bottom - top);
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ColorHelper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ColorHelper.cs
index 638256b0..20a003ad 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ColorHelper.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ColorHelper.cs
@@ -1,7 +1,10 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
+#if PSGFX
+#else
using PdfSharp.Drawing;
+#endif
using MigraDoc.DocumentObjectModel;
namespace MigraDoc.Rendering
@@ -11,6 +14,12 @@ static class ColorHelper
///
/// Converts Color to XColor.
///
+#if PSGFX
+ public static XColor ToXColor(Color color, bool cmyk)
+ {
+ return XColor.FromArgb((byte)color.A, (byte)color.R, (byte)color.G, (byte)color.B);
+ }
+#else
public static XColor ToXColor(Color color, bool cmyk)
{
if (color.IsEmpty)
@@ -18,7 +27,8 @@ public static XColor ToXColor(Color color, bool cmyk)
if (cmyk)
return XColor.FromCmyk(color.Alpha / 100.0, color.C / 100.0, color.M / 100.0, color.Y / 100.0, color.K / 100.0);
- return XColor.FromArgb((int)color.Argb);
+ return XColor.FromArgb((uint)color.Argb);
}
+#endif
}
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/DocumentRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/DocumentRenderer.cs
index 9dd9177f..bcf9e650 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/DocumentRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/DocumentRenderer.cs
@@ -1,16 +1,18 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using PdfSharp.Events;
using PdfSharp.Pdf;
+using PdfSharp.Internal;
+#if PSXGRA
using PdfSharp.Drawing;
+#endif
using MigraDoc.DocumentObjectModel;
using MigraDoc.DocumentObjectModel.Visitors;
using MigraDoc.DocumentObjectModel.Shapes;
using MigraDoc.DocumentObjectModel.Tables;
-using PdfSharp.Internal;
+using PdfSharp.Drawing;
namespace MigraDoc.Rendering
{
@@ -38,9 +40,11 @@ public void PrepareDocument(RenderEvents? renderEvents = null)
_previousListNumbers[ListType.NumberList2] = 0;
_previousListNumbers[ListType.NumberList3] = 0;
_formattedDocument = new FormattedDocument(_document, this);
-
+#if PSGFX
+ var gfx = (XGraphics)null!; //XGraphics.CreateMeasureContext(new XSize(2000, 2000), XGraphicsUnit.Point, XPageDirection.Downwards, renderEvents);
+#else
var gfx = XGraphics.CreateMeasureContext(new XSize(2000, 2000), XGraphicsUnit.Point, XPageDirection.Downwards, renderEvents);
-
+#endif
_previousListInfo = null;
_formattedDocument.Format(gfx);
}
@@ -96,7 +100,7 @@ public void RenderPage(XGraphics gfx, int page, PageRenderOptions options)
var fieldInfos = _formattedDocument.GetFieldInfos(page);
- fieldInfos.Date = PrintDate != DateTime.MinValue ? PrintDate : DateTime.Now;
+ fieldInfos.Date = PrintDate ?? DateTimeOffset.Now;
if ((options & PageRenderOptions.RenderHeader) == PageRenderOptions.RenderHeader)
RenderHeader(gfx, page);
@@ -164,7 +168,7 @@ documentObject is not Table &&
throw new ArgumentException(MdPdfMsgs.ObjectNotRenderable(documentObject.GetType().Name).Message);
var renderer = Renderer.Create(graphics, this, documentObject, null);
- renderer!.Format(new Rectangle(xPosition, yPosition, width, double.MaxValue), null);
+ renderer!.Format(new Rectangle(xPosition, yPosition, width, Single.MaxValue), null);
RenderInfo renderInfo = renderer.RenderInfo;
renderInfo.LayoutInfo.ContentArea.X = xPosition;
@@ -300,7 +304,7 @@ internal int NextListNumber(ListInfo listInfo)
///
/// Gets or sets the print date, i.e. the rendering date.
///
- public DateTime PrintDate { get; set; } = DateTime.MinValue;
+ public DateTimeOffset? PrintDate { get; set; } = null; // TODO: Breaking change, is now 'DateTimeOffset?'.
internal PredefinedFontsAndChars FontsAndChars => _fontsAndChars ??= new ();
PredefinedFontsAndChars? _fontsAndChars;
@@ -321,7 +325,12 @@ static XFont CreateFont(string familyName, double emSize, XFontStyleEx style, st
{
try
{
+#if PSGFX
+ //return new(familyName, emSize, style);
+ return null!;
+#else
return new(familyName, emSize, style);
+#endif
}
catch (Exception ex)
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FieldInfos.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FieldInfos.cs
index e29172e5..219d34bd 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FieldInfos.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FieldInfos.cs
@@ -61,6 +61,6 @@ internal BookmarkInfo(int physicalPageNumber, int displayPageNumber)
internal int Section;
internal int SectionPages;
internal int NumPages;
- internal DateTime Date;
+ internal DateTimeOffset Date;
}
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FillFormatRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FillFormatRenderer.cs
index 60f31199..b549d3f5 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FillFormatRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FillFormatRenderer.cs
@@ -1,10 +1,11 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using System.Diagnostics;
-using PdfSharp.Drawing;
using MigraDoc.DocumentObjectModel;
using MigraDoc.DocumentObjectModel.Shapes;
+#if !PSXGRA
+using PdfSharp.Drawing;
+#endif
namespace MigraDoc.Rendering
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs
index 349fe664..075e4e82 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs
@@ -3,31 +3,33 @@
#define CACHE_FONTS_
-using System.Diagnostics;
using PdfSharp.Drawing;
using MigraDoc.DocumentObjectModel;
+// v7.0.0 REVIEW PSG
+
namespace MigraDoc.Rendering
{
///
/// Helps to measure and handle fonts.
///
- class FontHandler
+ static class FontHandler
{
#if DEBUG_
internal static int CreateFontCounter;
#endif
-
///
/// Converts a DOM Font to an XFont.
///
internal static XFont FontToXFont(Font font)
{
- if (_lastXFont != null && font == _lastFont)
- return _lastXFont;
+ // Check if both WeakReferences are still valid and point to the font we need.
+ if (_lastXFont != null && _lastFont != null &&
+ _lastFont.TryGetTarget(out var lastFont) && font == lastFont &&
+ _lastXFont.TryGetTarget(out var lastXFont))
+ return lastXFont;
XFontStyleEx style = GetXStyle(font);
-
#if DEBUG_
if (StringComparer.OrdinalIgnoreCase.Compare(font.Name, "Segoe UI Semilight") == 0
&& (style & XFontStyleEx.BoldItalic) == XFontStyleEx.Italic)
@@ -37,13 +39,19 @@ internal static XFont FontToXFont(Font font)
#if DEBUG_
CreateFontCounter++;
#endif
- _lastFont = font;
- _lastXFont = xFont;
+ _lastFont = new(font);
+ _lastXFont = new(xFont);
+#if FORCE_MEMORYLEAK
+ _lastFont2 = font;
+#endif
return xFont;
}
- static XFont? _lastXFont;
- static Font? _lastFont;
+ static WeakReference? _lastXFont;
+ static WeakReference? _lastFont;
+#if FORCE_MEMORYLEAK
+ static Font? _lastFont2;
+#endif
internal static XFontStyleEx GetXStyle(Font font)
{
@@ -58,64 +66,87 @@ internal static XFontStyleEx GetXStyle(Font font)
internal static XUnitPt GetDescent(XFont font)
{
+#if PSGFX
+ var descent = (font.FontFace.Height - font.FontFace.Baseline) * font.Size;
+ return new(descent);
+#else
XUnitPt descent = font.Metrics.Descent;
descent *= font.Size;
descent /= font.FontFamily.GetEmHeight(font.Style);
return descent;
+#endif
}
internal static XUnitPt GetAscent(XFont font)
{
+#if PSGFX
+ var descent = font.FontFace.Baseline * font.Size;
+ return new(descent);
+#else
XUnitPt ascent = font.Metrics.Ascent;
ascent *= font.Size;
ascent /= font.FontFamily.GetEmHeight(font.Style);
return ascent;
+#endif
}
internal static double GetSubSuperScaling(XFont font)
{
+#if PSGFX
+ return 0.8 * font.Size; // TODO #PSG
+#else
return 0.8 * GetAscent(font) / font.GetHeight();
+#endif
}
internal static XFont ToSubSuperFont(XFont font)
{
+#if PSGFX
double size = font.Size * GetSubSuperScaling(font);
-
- return new XFont(font.Name2, size, font.Style, font.PdfOptions);
+ return new XFont(font.FontFace.FamilyName, size, font.Style/*, font.PdfOptions*/); // TODO #PSG
+#else
+ double size = font.Size * GetSubSuperScaling(font);
+ return new XFont(font.Name, size, font.Style, font.PdfOptions);
+#endif
}
internal static XBrush FontColorToXBrush(Font font)
{
+#if PSGFX
+ Debug.Assert(font.Document != null, "font.Document != null");
+ return new XSolidBrush(ColorHelper.ToXColor(font.Color, font.Document.UseCmykColor));
+#else
#if noCMYK
return new XSolidBrush(XColor.FromArgb((int)font.Color.A, (int)font.Color.R, (int)font.Color.G, (int)font.Color.B));
#else
Debug.Assert(font.Document != null, "font.Document != null");
return new XSolidBrush(ColorHelper.ToXColor(font.Color, font.Document.UseCmykColor));
+#endif
#endif
}
-#if CACHE_FONTS
- static XFont XFontFromCache(Font font, bool unicode, PdfFontEmbedding fontEmbedding)
- {
- XFont xFont = null;
+#if CACHE_FONTS || true_
+ static XFont XFontFromCache(Font font, bool unicode, PdfFontEmbedding fontEmbedding)
+ {
+ XFont xFont = null;
- XPdfFontOptions options = null;
- options = new XPdfFontOptions(fontEmbedding, unicode);
- XFontStyleEx style = GetXStyle(font);
- xFont = new XFont(font.Name, font.Size, style, options);
+ XPdfFontOptions options = null;
+ options = new XPdfFontOptions(fontEmbedding, unicode);
+ XFontStyleEx style = GetXStyle(font);
+ xFont = new XFont(font.Name, font.Size, style, options);
- return xFont;
- }
+ return xFont;
+ }
- static string BuildSignature(Font font, bool unicode, PdfFontEmbedding fontEmbedding)
- {
- StringBuilder signature = new StringBuilder(128);
- signature.Append(font.Name.ToLower());
- signature.Append(font.Size.Point.ToString("##0.0"));
- return signature.ToString();
- }
+ static string BuildSignature(Font font, bool unicode, PdfFontEmbedding fontEmbedding)
+ {
+ StringBuilder signature = new StringBuilder(128);
+ signature.Append(font.Name.ToLower());
+ signature.Append(font.Size.Point.ToString("##0.0"));
+ return signature.ToString();
+ }
- static Hash_table fontCache = new Hash_table();
+ static Hash_table fontCache = new Hash_table();
#endif
}
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedDocument.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedDocument.cs
index b9239d0e..3c9c8b24 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedDocument.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedDocument.cs
@@ -190,7 +190,7 @@ Rectangle CalcContentRect(int page)
width -= pageSetup.RightMargin.Point;
width -= pageSetup.LeftMargin.Point;
-
+
XUnitPt height = pageSetup.PageHeight.Point;
height -= pageSetup.TopMargin.Point;
@@ -423,7 +423,7 @@ PageOrientation CalcPageOrientation(PageSetup _)
}
XSize CalcPageSize(PageSetup pageSetup)
- => new(pageSetup.PageWidth.Point, pageSetup.PageHeight.Point);
+ => new((float_)pageSetup.PageWidth.Point, (float_)pageSetup.PageHeight.Point);
bool IAreaProvider.PositionHorizontally(LayoutInfo layoutInfo)
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedHeaderFooter.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedHeaderFooter.cs
index 4eb45812..64940be9 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedHeaderFooter.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedHeaderFooter.cs
@@ -30,7 +30,7 @@ internal void Format(XGraphics gfx)
Area? IAreaProvider.GetNextArea()
{
if (_isFirstArea)
- return new Rectangle(ContentRect.X, ContentRect.Y, ContentRect.Width, double.MaxValue);
+ return new Rectangle(ContentRect.X, ContentRect.Y, ContentRect.Width, FLOAT_.MaxValue);
return null;
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedTextArea.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedTextArea.cs
index 74cd37e9..eaa49d82 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedTextArea.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedTextArea.cs
@@ -70,8 +70,8 @@ XUnitPt CalcInherentWidth()
inherentWidth = Math.Max(renderer.RenderInfo.LayoutInfo.MinWidth, inherentWidth);
}
}
- inherentWidth.Point += TextArea.LeftPadding.Point;
- inherentWidth.Point += TextArea.RightPadding.Point;
+ inherentWidth.Point += (float_)TextArea.LeftPadding.Point;
+ inherentWidth.Point += (float_)TextArea.RightPadding.Point;
return inherentWidth;
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/IAreaProvider.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/IAreaProvider.cs
index eb5d4d2b..be957814 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/IAreaProvider.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/IAreaProvider.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
namespace MigraDoc.Rendering
@@ -14,7 +14,7 @@ interface IAreaProvider
Area? GetNextArea();
///
- /// Probes the next area to render into like GetNextArea, but doesnt change the provider state.
+ /// Probes the next area to render into like GetNextArea, but doesn’t change the provider state.
///
/// The area for the next rendering act.
Area? ProbeNextArea();
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs
index d7f70d17..c2bbb255 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs
@@ -1,7 +1,6 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using System.Diagnostics;
using Microsoft.Extensions.Logging;
using PdfSharp.Drawing;
using MigraDoc.DocumentObjectModel.Shapes;
@@ -86,21 +85,20 @@ internal override void Render()
}
catch (Exception)
{
- RenderFailureImage(destRect);
+ RenderFailureImage(destRect, formatInfo.ImagePath);
}
finally
{
- if (xImage != null)
- xImage.Dispose();
+ xImage?.Dispose();
}
}
else
- RenderFailureImage(destRect);
+ RenderFailureImage(destRect, formatInfo.ImagePath);
RenderLine();
}
- void RenderFailureImage(XRect destRect)
+ void RenderFailureImage(XRect destRect, string? imagePath)
{
_gfx.DrawRectangle(XBrushes.LightGray, destRect);
string failureString;
@@ -113,7 +111,7 @@ void RenderFailureImage(XRect destRect)
break;
case ImageFailure.FileNotFound:
- failureString = MdPdfMsgs.DisplayImageFileNotFound("???").Message;
+ failureString = MdPdfMsgs.DisplayImageFileNotFound(imagePath ?? "Unknown image").Message;
break;
case ImageFailure.InvalidType:
@@ -188,9 +186,9 @@ void CalculateImageDimensions()
}
// ReSharper restore CompareOfFloatsByEqualityOperator
- XUnitPt inherentWidth = XUnitPt.FromInch(xPixels / horzRes);
+ XUnitPt inherentWidth = XUnitPt.FromInch((float_)(xPixels / horzRes));
double yPixels = xImage.PixelHeight;
- XUnitPt inherentHeight = XUnitPt.FromInch(yPixels / vertRes);
+ XUnitPt inherentHeight = XUnitPt.FromInch((float_)(yPixels / vertRes));
//bool lockRatio = _image.IsNull("LockAspectRatio") ? true : _image.LockAspectRatio;
bool lockRatio = _image.Values.LockAspectRatio is null || _image.LockAspectRatio;
@@ -275,8 +273,8 @@ void CalculateImageDimensions()
}
if (resultHeight <= 0 || resultWidth <= 0)
{
- formatInfo.Width = XUnitPt.FromCentimeter(2.5);
- formatInfo.Height = XUnitPt.FromCentimeter(2.5);
+ formatInfo.Width = XUnitPt.FromCentimeter(2.5f);
+ formatInfo.Height = XUnitPt.FromCentimeter(2.5f);
//Debug.WriteLine(Messages2.EmptyImageSize);
MigraDocLogHost.PdfRenderingLogger.LogError(MdPdfMsgs.EmptyImageSize.Message);
_failure = ImageFailure.EmptySize;
@@ -305,13 +303,13 @@ void CalculateImageDimensions()
if (!_image.Values.Width.IsValueNullOrEmpty())
formatInfo.Width = _image.Width.Point;
else
- formatInfo.Width = XUnitPt.FromCentimeter(2.5);
+ formatInfo.Width = XUnitPt.FromCentimeter(2.5f);
//if (_image.Values.Height is not null)
if (!_image.Values.Height.IsValueNullOrEmpty())
formatInfo.Height = _image.Height.Point;
else
- formatInfo.Height = XUnitPt.FromCentimeter(2.5);
+ formatInfo.Height = XUnitPt.FromCentimeter(2.5f);
}
}
@@ -326,7 +324,7 @@ XImage CreateXImage(string uri)
// Same for GDI.
// We have to rely on the garbage collector to properly dispose the MemoryStream.
{
- Stream stream = new MemoryStream(bytes);
+ Stream stream = new MemoryStream(bytes, 0, bytes.Length, false, true);
XImage image = XImage.FromStream(stream);
return image;
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/LineFormatRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/LineFormatRenderer.cs
index a50d57f6..925a5efb 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/LineFormatRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/LineFormatRenderer.cs
@@ -72,11 +72,19 @@ internal void Render(XUnitPt xPosition, XUnitPt yPosition, XUnitPt width, XUnitP
var pen = new XPen(GetColor(), width);
pen.DashStyle = (_lineFormat?.DashStyle ?? NRT.ThrowOnNull()) switch
{
+#if PSGFX
+ DashStyle.Dash => XDashStyles.Dash,
+ DashStyle.DashDot => XDashStyles.DashDot,
+ DashStyle.DashDotDot => XDashStyles.DashDotDot,
+ DashStyle.Solid => XDashStyles.Solid,
+ DashStyle.SquareDot => XDashStyles.Dot,
+#else
DashStyle.Dash => XDashStyle.Dash,
DashStyle.DashDot => XDashStyle.DashDot,
DashStyle.DashDotDot => XDashStyle.DashDotDot,
DashStyle.Solid => XDashStyle.Solid,
DashStyle.SquareDot => XDashStyle.Dot,
+#endif
_ => pen.DashStyle
};
return pen;
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsg.resx b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsg.resx
index 614232cc..5cc5f3da 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsg.resx
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsg.resx
@@ -127,7 +127,7 @@
Image could not be read.
- The number {0} is to large to be displayed as letters.
+ The number {0} is too large to be displayed as letters.
Bookmark '{0}' is not defined within the document.
@@ -136,7 +136,7 @@
Invalid image type: '{0}'.
- The number {0} is to large to be displayed as roman.
+ The number {0} is too large to be displayed as roman.
Image '{0}' not found.
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.cs
index dd207cc1..60ebc9cd 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.cs
@@ -6,7 +6,7 @@
namespace MigraDoc.Rendering
{
///
- /// MigraDoc PDF renderer message id.
+ /// MigraDoc PDF renderer message ID.
///
// ReSharper disable InconsistentNaming
enum MdPdfMsgId
@@ -36,10 +36,10 @@ enum MdPdfMsgId
static class MdPdfMsgs
{
internal static MdPdfMsg NumberTooLargeForRoman(int number)
- => new(MdPdfMsgId.NumberTooLargeForRoman, Invariant($"The number {number} is to large to be displayed as roman number."));
+ => new(MdPdfMsgId.NumberTooLargeForRoman, Invariant($"The number {number} is too large to be displayed as roman number."));
internal static MdPdfMsg NumberTooLargeForLetters(int number)
- => new(MdPdfMsgId.NumberTooLargeForLetters, $"The number {number} is to large to be displayed as letters.");
+ => new(MdPdfMsgId.NumberTooLargeForLetters, $"The number {number} is too large to be displayed as letters.");
internal static MdPdfMsg DisplayEmptyImageSize
=> new(MdPdfMsgId.DisplayEmptyImageSize, "Image has empty size.");
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.resx b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.resx
index 614232cc..5cc5f3da 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.resx
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.resx
@@ -127,7 +127,7 @@
Image could not be read.
- The number {0} is to large to be displayed as letters.
+ The number {0} is too large to be displayed as letters.
Bookmark '{0}' is not defined within the document.
@@ -136,7 +136,7 @@
Invalid image type: '{0}'.
- The number {0} is to large to be displayed as roman.
+ The number {0} is too large to be displayed as roman.
Image '{0}' not found.
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MigraDocRenderingBuildInformation.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MigraDocRenderingBuildInformation.cs
index 32e34786..8e0e04c7 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MigraDocRenderingBuildInformation.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MigraDocRenderingBuildInformation.cs
@@ -1,63 +1,40 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using System.Reflection;
-using System.Runtime.Versioning;
-
-#pragma warning disable 0436
-
-namespace MigraDoc.Rendering
+namespace MigraDoc.Rendering // DELETE
{
///
- /// Information about the build of the renderer.
+ /// Use MigraDocProductVersionInformation instead.
///
+ [Obsolete("Do not use this class anymore. It is not useful and will be removed.")]
public static class MigraDocRenderingBuildInformation
{
///
- /// Gets the git semantic version number created by GitVersionTask.
+ /// Gets the git semantic version number.
///
- public static string GitSemVer => global::GitVersionInformation.SemVer;
+ [Obsolete("Do not use this class anymore. Use 'PdfSharp.Internal.SemVersionInformation.Version' instead.")]
+ public static string GitSemVer => "do not use this anymore";
///
- /// Gets the name of the branch created by GitVersionTask.
+ /// Gets the name of the branch.
///
- public static string BranchName => global::GitVersionInformation.BranchName;
+ [Obsolete("Do not use this class anymore. Use 'PdfSharp.Internal.SemVersionInformation.BranchName' instead.")]
+ public static string BranchName => "do not use this anymore";
///
- /// Gets the commit date created by GitVersionTask.
+ /// Gets the commit date.
///
- public static string CommitDate => global::GitVersionInformation.CommitDate;
+ [Obsolete("Do not use this class anymore. Use 'PdfSharp.Internal.SemVersionInformation.CommitDate' instead.")]
+ public static string CommitDate => "do not use this anymore";
///
/// Gets the assembly title attribute value.
///
- public static string AssemblyTitle
- => ((AssemblyTitleAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false)[0]).Title;
+ public static string AssemblyTitle => "do not use this anymore";
-#if !NET6_0_OR_GREATER
- ///
- /// Gets the target platform attribute value.
- ///
- public static string TargetPlatform
- {
- get
- {
- // TargetPlatformAttribute is not available under .NET Framework.
- return "Unknown Target Platform";
- }
- }
-#else
///
/// Gets the target platform attribute value.
///
- public static string TargetPlatform
- {
- get
- {
- var attribute = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(TargetPlatformAttribute), false);
- return attribute.Length == 1 ? ((TargetPlatformAttribute)attribute[0]).PlatformName : "Unknown Target Platform";
- }
- }
-#endif
+ public static string TargetPlatform => "do not use this anymore";
}
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphIterator.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphIterator.cs
index bc231bb3..5b65013f 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphIterator.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphIterator.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using MigraDoc.DocumentObjectModel;
@@ -98,7 +98,7 @@ internal bool IsLastLeaf
/// Returns the next iterator in the tree pointing to a leaf.
///
/// This function is intended to receive the renderable objects of a paragraph.
- /// Thus, empty ParagraphElement objects (which are collections) dont count as leafs.
+ /// Thus, empty ParagraphElement objects (which are collections) don’t count as leafs.
internal ParagraphIterator? GetNextLeaf()
{
// Move up to appropriate parent element.
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs
index e6d9ec52..5bf7b721 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs
@@ -1,12 +1,11 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using System.Diagnostics;
using System.Text;
-using MigraDoc.DocumentObjectModel;
using PdfSharp.Pdf;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Drawing;
+using MigraDoc.DocumentObjectModel;
using MigraDoc.DocumentObjectModel.Fields;
using MigraDoc.DocumentObjectModel.Shapes;
using MigraDoc.Rendering.Extensions;
@@ -115,7 +114,7 @@ internal override void Render()
for (int idx = 0; idx < parFormatInfo.LineCount; idx++)
{
LineInfo lineInfo = parFormatInfo.GetLineInfo(idx);
- _isLastLine = (idx == parFormatInfo.LineCount - 1);
+ _isLastLine = idx == parFormatInfo.LineCount - 1;
_lastTabPosition = 0;
if (lineInfo.ReMeasureLine)
@@ -179,9 +178,9 @@ string GetFieldValue(DocumentObject field)
{
if (field is DateField dateField)
{
- var dt = _fieldInfos?.Date ?? NRT.ThrowOnNull();
- if (dt == DateTime.MinValue)
- dt = DateTime.Now;
+ var dt = _fieldInfos?.Date ?? NRT.ThrowOnNull();
+ if (dt == DateTimeOffset.MinValue)
+ dt = DateTimeOffset.Now;
return FormatDateTimeForField(dt, dateField);
}
@@ -194,7 +193,7 @@ string GetFieldValue(DocumentObject field)
return "";
}
- static String FormatDateTimeForField(DateTime dateTime, DateField dateField)
+ static String FormatDateTimeForField(DateTimeOffset dateTime, DateField dateField)
{
var culture = dateField.Document!.EffectiveCulture;
var dtfInfo = culture.DateTimeFormat;
@@ -361,7 +360,7 @@ static bool IsSoftHyphen(DocumentObject docObj)
XUnitPt ProbeAfterLeftAlignedTab(XUnitPt tabStopPosition, out bool notFitting)
{
//--- Save ---------------------------------
- SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
+ SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
out var lineEndsWithLineBreak);
//------------------------------------------
@@ -387,7 +386,7 @@ XUnitPt ProbeAfterLeftAlignedTab(XUnitPt tabStopPosition, out bool notFitting)
XUnitPt ProbeAfterRightAlignedTab(XUnitPt tabStopPosition, out bool notFitting)
{
//--- Save ---------------------------------
- SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
+ SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
out var lineEndsWithLineBreak);
//------------------------------------------
@@ -431,7 +430,7 @@ XUnitPt ProbeAfterRightAlignedTab(XUnitPt tabStopPosition, out bool notFitting)
XUnitPt ProbeAfterCenterAlignedTab(XUnitPt tabStopPosition, out bool notFitting)
{
//--- Save ---------------------------------
- SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
+ SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
out var lineEndsWithLineBreak);
//------------------------------------------
@@ -475,7 +474,7 @@ XUnitPt ProbeAfterDecimalAlignedTab(XUnitPt tabStopPosition, out bool notFitting
notFitting = false;
//--- Save ---------------------------------
- SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
+ SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
out var lineEndsWithLineBreak);
//------------------------------------------
@@ -571,7 +570,7 @@ XUnitPt ProbeAfterDecimalAlignedTab(XUnitPt tabStopPosition, out bool notFitting
return ProbeAfterRightAlignedTab(tabStopPosition, out notFitting);
}
- void SaveBeforeProbing(out ParagraphIterator? paragraphIter, out int blankCount, out XUnitPt wordsWidth, out XUnitPt xPosition, out XUnitPt lineWidth, out XUnitPt blankWidth,
+ void SaveBeforeProbing(out ParagraphIterator? paragraphIter, out int blankCount, out XUnitPt wordsWidth, out XUnitPt xPosition, out XUnitPt lineWidth, out XUnitPt blankWidth,
out bool lineEndsWithLineBreak)
{
paragraphIter = _currentLeaf;
@@ -583,7 +582,7 @@ void SaveBeforeProbing(out ParagraphIterator? paragraphIter, out int blankCount,
lineEndsWithLineBreak = _currentLineEndsWithLineBreak;
}
- void RestoreAfterProbing(ParagraphIterator? paragraphIter, int blankCount, XUnitPt wordsWidth, XUnitPt xPosition, XUnitPt lineWidth, XUnitPt blankWidth,
+ void RestoreAfterProbing(ParagraphIterator? paragraphIter, int blankCount, XUnitPt wordsWidth, XUnitPt xPosition, XUnitPt lineWidth, XUnitPt blankWidth,
bool lineEndsWithLineBreak)
{
_currentLeaf = paragraphIter;
@@ -606,7 +605,7 @@ bool ProbeAfterTab()
_currentBlankCount = 0;
// Extra for auto tab after list symbol.
- //TODO_OLD: KLPO4KLPO: Check if this conditional statement is still required.
+ // Check if this conditional statement is still required.
if (_currentLeaf != null && IsTab(_currentLeaf.Current))
_currentLeaf = _currentLeaf.GetNextLeaf();
@@ -820,7 +819,7 @@ void RenderLine(LineInfo lineInfo)
void ReMeasureLine(ref LineInfo lineInfo)
{
//--- Save ---------------------------------
- SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
+ SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
out var lineEndsWithLineBreak);
bool origLastTabPassed = _lastTabPassed;
//------------------------------------------
@@ -957,7 +956,7 @@ void RenderElement(DocumentObject docObj)
break;
default:
- throw new NotImplementedException(docObj.GetType().Name + " is not implemented.");
+ throw new NotSupportedException(docObj.GetType().Name + " is not implemented.");
}
}
@@ -1021,7 +1020,7 @@ void RenderBookmarkField(BookmarkField bookmarkField)
var destinationName = bookmarkField.Name;
var position = GetDestinationPosition();
- pdfDocument.AddNamedDestination(destinationName, pageNr, PdfNamedDestinationParameters.CreatePosition(position));
+ pdfDocument.AddNamedDestination(destinationName, pageNr, PdfNamedDestinationParameters.CreatePosition(position.AsXPoint));
}
RenderUnderline(0, false);
@@ -1043,7 +1042,7 @@ XPoint GetDestinationPosition()
return pdfPosition;
}
// ReSharper disable once InconsistentNaming
- static readonly XUnitPt _margin = XUnitPt.FromCentimeter(0.5);
+ static readonly XUnitPt _margin = XUnitPt.FromCentimeter(0.5f);
void RenderPageRefField(PageRefField pageRefField)
{
@@ -1268,31 +1267,31 @@ void EndHyperlink(Hyperlink hyperlink, XUnitPt right, XUnitPt bottom)
var pdfDocument = _gfx.PdfPage?.Owner;
if (pdfDocument != null)
{
- page.AddDocumentLink(new PdfRectangle(rect), hyperlink.BookmarkName);
+ page.AddDocumentLink(new PdfRectangle(rect.AsXRect), hyperlink.BookmarkName);
}
// Otherwise use page from bookmark’s fieldInfo.
else
{
var pageRef = _fieldInfos?.GetPhysicalPageNumber(hyperlink.BookmarkName) ?? NRT.ThrowOnNull();
if (pageRef > 0)
- page.AddDocumentLink(new PdfRectangle(rect), pageRef);
+ page.AddDocumentLink(new PdfRectangle(rect.AsXRect), pageRef);
}
break;
case HyperlinkType.ExternalBookmark:
- page.AddDocumentLink(new PdfRectangle(rect), hyperlink.Filename, hyperlink.BookmarkName, ConvertHyperlinkTargetWindow(hyperlink.NewWindow));
+ page.AddDocumentLink(new PdfRectangle(rect.AsXRect), hyperlink.Filename, hyperlink.BookmarkName, ConvertHyperlinkTargetWindow(hyperlink.NewWindow));
break;
case HyperlinkType.EmbeddedDocument:
- page.AddEmbeddedDocumentLink(new PdfRectangle(rect), hyperlink.Filename, hyperlink.BookmarkName, ConvertHyperlinkTargetWindow(hyperlink.NewWindow));
+ page.AddEmbeddedDocumentLink(new PdfRectangle(rect.AsXRect), hyperlink.Filename, hyperlink.BookmarkName, ConvertHyperlinkTargetWindow(hyperlink.NewWindow));
break;
case HyperlinkType.Web:
- page.AddWebLink(new PdfRectangle(rect), hyperlink.Filename);
+ page.AddWebLink(new PdfRectangle(rect.AsXRect), hyperlink.Filename);
break;
case HyperlinkType.File:
- page.AddFileLink(new PdfRectangle(rect), hyperlink.Filename);
+ page.AddFileLink(new PdfRectangle(rect.AsXRect), hyperlink.Filename);
break;
}
_hyperlinkRect = new XRect();
@@ -1905,7 +1904,9 @@ FormatResult FormatAsWord(XUnitPt width, Rectangle fittingRect)
FormatResult FormatDateField(DateField dateField, Rectangle fittingRect)
{
_reMeasureLine = true;
- var estimatedFieldValue = FormatDateTimeForField(DateTime.Now, dateField);
+ // Use a fixed date to measure the dimensions required.
+ var estimatedFieldValue = FormatDateTimeForField(new DateTimeOffset(2999, 08, 28, 22, 28, 28, TimeSpan.Zero), dateField);
+ //var estimatedFieldValue = FormatDateTimeForField(DateTimeOffset.Now, dateField);
return FormatWord(estimatedFieldValue, fittingRect);
}
@@ -2221,7 +2222,7 @@ FormatResult FormatSoftHyphen()
return FormatResult.Continue;
//--- Save ---------------------------------
- SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
+ SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth,
out var lineEndsWithLineBreak);
//------------------------------------------
_currentLeaf = nextIter;
@@ -2367,7 +2368,7 @@ void StoreLineInformation()
if (_startLeaf != null && _startLeaf == _currentLeaf)
HandleNonFittingLine();
#endif
-
+
lineInfo.LastTab = _lastTab;
_renderInfo.LayoutInfo.ContentArea = contentArea ?? NRT.ThrowOnNull();
@@ -2511,7 +2512,7 @@ VerticalLineInfo CalcVerticalInfo(XFont font, bool isListSymbol = false)
lineHeight = singleLineSpace * factor;
lineHeight = Math.Max(lineHeight, _currentVerticalInfo.Height);
}
-
+
XUnitPt descent;
if (lineSpacingRule == LineSpacingRule.Exactly)
{
@@ -2541,7 +2542,7 @@ VerticalLineInfo CalcVerticalInfo(XFont font, bool isListSymbol = false)
return new(lineHeight, descent, inherentLineSpace);
}
-
+
///
/// The font used for the current paragraph element.
///
@@ -2676,7 +2677,11 @@ void EndUnderline(XPen pen, XUnitPt xPosition)
{
XUnitPt yPosition = CurrentBaselinePosition;
yPosition += 0.33 * _currentVerticalInfo.Descent;
+#if PSGFX
+ _gfx.DrawLine(pen, new(_underlineStartPos, yPosition), new(xPosition, yPosition));
+#else
_gfx.DrawLine(pen, _underlineStartPos, yPosition, xPosition, yPosition);
+#endif
}
XPen? _currentUnderlinePen;
@@ -2739,16 +2744,30 @@ bool UnderlinePenChanged(XPen? pen)
XPen pen = new XPen(XColor.FromArgb(font.Color.Argb), font.Size / 16);
#else
Debug.Assert(_paragraph.Document != null, "_paragraph.Document != null");
+#if PSGFX
+ var xxx = ColorHelper.ToXColor(font.Color, _paragraph.Document.UseCmykColor);
+ var pen = new XPen(new XSolidBrush(xxx), (float_)font.Size.Point / 16);
+#else
var pen = new XPen(ColorHelper.ToXColor(font.Color, _paragraph.Document.UseCmykColor), font.Size.Point / 16);
+#endif
#endif
pen.DashStyle = font.Underline switch
{
+#if PSGFX
+ Underline.DotDash => XDashStyles.DashDot,
+ Underline.DotDotDash => XDashStyles.DashDotDot,
+ Underline.Dash => XDashStyles.Dash,
+ Underline.Dotted => XDashStyles.Dot,
+ Underline.Single => XDashStyles.Solid,
+ _ => XDashStyles.Solid
+#else
Underline.DotDash => XDashStyle.DashDot,
Underline.DotDotDash => XDashStyle.DashDotDot,
Underline.Dash => XDashStyle.Dash,
Underline.Dotted => XDashStyle.Dot,
Underline.Single => XDashStyle.Solid,
_ => XDashStyle.Solid
+#endif
};
return pen;
}
@@ -2769,7 +2788,7 @@ bool UnderlinePenChanged(XPen? pen)
bool _isFirstLine;
bool _isLastLine;
VerticalLineInfo _currentVerticalInfo;
- Area _formattingArea = default!;
+ Area _formattingArea = null!;
XUnitPt _currentYPosition;
XUnitPt _currentXPosition;
ParagraphIterator? _currentLeaf;
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/PdfDocumentRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/PdfDocumentRenderer.cs
index d0d6c4f2..c3cc1337 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/PdfDocumentRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/PdfDocumentRenderer.cs
@@ -1,12 +1,17 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using System.Diagnostics;
using System.Reflection;
using PdfSharp.Pdf;
using PdfSharp.Drawing;
using MigraDoc.DocumentObjectModel;
using MigraDoc.Rendering.Internals;
+#if PSGFX
+using PdfSharp.Graphics.Pdf;
+using PdfSharp.Graphics.Pdf.Media;
+
+#else
+#endif
namespace MigraDoc.Rendering
{
@@ -143,10 +148,12 @@ public void PrepareRenderPages()
var pdfDocument = PdfDocument;
foreach (var item in _document?.EmbeddedFiles ?? NRT.ThrowOnNull())
{
- if (item as EmbeddedFile is { } embeddedFile)
- pdfDocument.AddEmbeddedFile(embeddedFile.Name, embeddedFile.Path);
+ if (item as DocumentObjectModel.EmbeddedFile is { } embeddedFile)
+#pragma warning disable CS0618 // Type or member is obsolete
+ pdfDocument.AddEmbeddedFile(embeddedFile.Name, embeddedFile.Path); // TODO: Do not use this function anymore.
+#pragma warning restore CS0618 // Type or member is obsolete
else
- NRT.ThrowOnNull();
+ NRT.ThrowOnNull();
}
WriteDocumentInformation();
@@ -200,17 +207,31 @@ public void RenderPages(int startPage, int endPage)
_pdfDocument ??= CreatePdfDocument();
- DocumentRenderer.PrintDate = DateTime.Now;
+ DocumentRenderer.PrintDate = DateTimeOffset.Now;
for (int pageNr = startPage; pageNr <= endPage; ++pageNr)
{
var pdfPage = _pdfDocument.AddPage();
var pageInfo = DocumentRenderer.FormattedDocument.GetPageInfo(pageNr);
+#if PSGFX
+ pdfPage.Width = PdfSharp.Drawing.XUnit.FromPoint(pageInfo.Width);
+ pdfPage.Height = PdfSharp.Drawing.XUnit.FromPoint(pageInfo.Height);
+#else
pdfPage.Width = pageInfo.Width;
pdfPage.Height = pageInfo.Height;
+#endif
pdfPage.Orientation = pageInfo.Orientation;
+#if PSGFX
+ var pageVisual = PdfPageVisual.FromPdfPage(pdfPage);
+ var gfx = pageVisual.RenderOpen();
+#else
using var gfx = XGraphics.FromPdfPage(pdfPage);
+#endif
DocumentRenderer.RenderPage(gfx, pageNr);
+#if PSGFX
+ // DrawingContext must be closed.
+ gfx.Close();
+#endif
}
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ShadingRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ShadingRenderer.cs
index 08f32f85..d18dc727 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ShadingRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ShadingRenderer.cs
@@ -42,6 +42,7 @@ internal void Render(XUnitPt x, XUnitPt y, XUnitPt width, XUnitPt height, Rounde
var path = new XGraphicsPath();
+#if !PSGFX
switch (roundedCorner)
{
case RoundedCorner.TopLeft:
@@ -61,6 +62,7 @@ internal void Render(XUnitPt x, XUnitPt y, XUnitPt width, XUnitPt height, Rounde
path.AddLine(new XPoint(x, y), new XPoint(x + width, y));
break;
}
+#endif
path.CloseFigure();
_gfx.DrawPath(_brush, path);
}
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableFormatInfo.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableFormatInfo.cs
index 227e97ef..3a0cdd50 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableFormatInfo.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableFormatInfo.cs
@@ -3,7 +3,9 @@
using MigraDoc.DocumentObjectModel.Tables;
using MigraDoc.DocumentObjectModel.Visitors;
+#if !PSXGRA
using PdfSharp.Drawing;
+#endif
namespace MigraDoc.Rendering
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableRenderer.cs
index a28b6406..9e780de2 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableRenderer.cs
@@ -1,7 +1,6 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using System.Diagnostics;
using MigraDoc.DocumentObjectModel.Internals;
using PdfSharp.Drawing;
using MigraDoc.DocumentObjectModel;
@@ -216,25 +215,25 @@ void RenderContent(Cell cell, Rectangle innerRect)
XUnitPt contentHeight = formattedCell.ContentHeight;
XUnitPt innerHeight = innerRect.Height;
Debug.Assert(cell.Column != null, "cell.Column != null");
- XUnitPt targetX = innerRect.X + cell.Column.LeftPadding.Point;
+ XUnitPt targetX = (float_)(innerRect.X + cell.Column.LeftPadding.Point);
XUnitPt targetY;
Debug.Assert(cell.Row != null, "cell.Row != null");
if (verticalAlignment == VerticalAlignment.Bottom)
{
targetY = innerRect.Y + innerRect.Height;
- targetY -= cell.Row.BottomPadding.Point;
+ targetY -= (float_)cell.Row.BottomPadding.Point;
targetY -= contentHeight;
}
else if (verticalAlignment == VerticalAlignment.Center)
{
- targetY = innerRect.Y + cell.Row.TopPadding.Point;
- targetY += innerRect.Y + innerRect.Height - cell.Row.BottomPadding.Point;
+ targetY = (float_)(innerRect.Y + cell.Row.TopPadding.Point);
+ targetY += (float_)(innerRect.Y + innerRect.Height - cell.Row.BottomPadding.Point);
targetY -= contentHeight;
targetY /= 2;
}
else
- targetY = innerRect.Y + cell.Row.TopPadding.Point;
+ targetY = (float_)(innerRect.Y + cell.Row.TopPadding.Point);
RenderByInfos(targetX, targetY, renderInfos);
}
@@ -283,7 +282,7 @@ Rectangle GetInnerRect(XUnitPt startingHeight, Cell cell)
int cellColIndex = cell.Column.Index; // Cache property result.
for (int clmIdx = 0; clmIdx < cellColIndex; ++clmIdx)
{
- x += _table.Columns[clmIdx]?.Width.Point ?? NRT.ThrowOnNull();
+ x += (float_)(_table.Columns[clmIdx]?.Width.Point ?? NRT.ThrowOnNull());
}
x += LeftBorderOffset;
@@ -491,7 +490,7 @@ void FinishLayoutInfo(Area area, XUnitPt currentHeight, XUnitPt startingHeight)
//foreach (Column clm in _table.Columns)
foreach (var clm in _table.Columns.Cast())
{
- width += clm.Width.Point;
+ width += (float_)(clm.Width.Point);
}
layoutInfo.ContentArea.Width = width;
}
@@ -499,12 +498,12 @@ void FinishLayoutInfo(Area area, XUnitPt currentHeight, XUnitPt startingHeight)
//if (_table.Rows.Values.LeftIndent is not null)
if (!_table.Rows.Values.LeftIndent.IsValueNullOrEmpty())
- layoutInfo.Left = _table.Rows.LeftIndent.Point;
+ layoutInfo.Left = (float_)_table.Rows.LeftIndent.Point;
else if (_table.Rows.Alignment == RowAlignment.Left)
{
XUnitPt leftOffset = LeftBorderOffset;
- leftOffset += _table.Columns[0].LeftPadding.Point;
+ leftOffset += (float_)_table.Columns[0].LeftPadding.Point;
layoutInfo.Left = -leftOffset;
}
@@ -590,7 +589,7 @@ void CalcLastHeaderRow()
_lastHeaderRow = row.Index;
else break;
}
- // Note: Do not use _connectedRowsMap here, it was not yet initialized.
+ // Do not use _connectedRowsMap here, it was not yet initialized.
if (_lastHeaderRow >= 0)
_lastHeaderRow = CalcLastConnectedRowDirect(_lastHeaderRow);
@@ -876,7 +875,7 @@ Cell GetMinMergedCell(int row)
{
#if DEBUG
// Comparing the result of the new implementation with the old version.
- // Note: It does not matter which cell is actually returned. Just the bottom margin of the cell matters.
+ // It does not matter which cell is actually returned. Just the bottom margin of the cell matters.
var originalResult = GetMinMergedCellOriginal(row);
#endif
@@ -896,7 +895,7 @@ Cell GetMinMergedCell(int row)
// Does it matter which cell we return??? 2023-03-08 Yes, we need a result that is in _mergedCells.
Debug.Assert(originalResult == cell);
- // Note: It does not matter which cell is actually returned. Just the bottom margin of the cell matters.
+ // It does not matter which cell is actually returned. Just the bottom margin of the cell matters.
Debug.Assert(originalResult.Row!.Index + originalResult.MergeDown ==
cell.Row!.Index + cell.MergeDown);
#endif
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TextFrameRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TextFrameRenderer.cs
index b254fbdb..4cd2f44a 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TextFrameRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TextFrameRenderer.cs
@@ -60,7 +60,11 @@ void RenderContent()
XGraphicsState Transform()
{
Area frameContentArea = _renderInfo.LayoutInfo.ContentArea;
+#if PSGFX
+ var state = _gfx.Save();
+#else
XGraphicsState state = _gfx.Save();
+#endif
XUnitPt xPosition;
XUnitPt yPosition;
switch (_textFrame.Orientation)
@@ -93,8 +97,13 @@ XGraphicsState Transform()
void ResetTransform(XGraphicsState? state)
{
+#if PSGFX
+ if (state != null)
+ _gfx.Restore(state.Value);
+#else
if (state != null)
_gfx.Restore(state);
+#endif
}
readonly TextFrame _textFrame;
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TopDownFormatter.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TopDownFormatter.cs
index 2ded913e..277478d1 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TopDownFormatter.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TopDownFormatter.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using MigraDoc.DocumentObjectModel;
@@ -66,7 +66,7 @@ public void FormatOnAreas(XGraphics gfx, bool topLevel)
{
if (docObj is Paragraph p && p.Format.SpaceBefore > Unit.Zero)
{
- // IsFirstOnPage isnt true for an element in _elements following a PageBreak. IsFirstOnRenderedPage is also true if the last element was a PageBreak.
+ // IsFirstOnPage isn’t true for an element in _elements following a PageBreak. IsFirstOnRenderedPage is also true if the last element was a PageBreak.
bool isFirstOnRenderedPage = isFirstOnPage || idx > 0 && _elements[idx - 1] is PageBreak;
if (isFirstOnRenderedPage)
p.Format.SpaceBefore = Unit.Zero;
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj
index 6f390de3..262cfd21 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj
@@ -1,6 +1,6 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
MigraDoc
GDI
@@ -8,6 +8,7 @@
OnBuildSuccess
True
..\..\..\..\..\StrongnameKey.snk
+ true
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj
index 0eec2a3f..ff82545c 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj
@@ -1,6 +1,6 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
MigraDoc
WPF
@@ -9,6 +9,7 @@
OnBuildSuccess
True
..\..\..\..\..\StrongnameKey.snk
+ true
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj
index 10518896..317a7aba 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj
@@ -1,7 +1,7 @@
- net6.0;net8.0;netstandard2.0
+ $(PDFsharpTargetFrameworks_Library)
MigraDoc
..\..\..\..\..\StrongnameKey.snk
True
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/CharacterRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/CharacterRenderer.cs
index 9550de19..a78a4d52 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/CharacterRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/CharacterRenderer.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using System.Diagnostics;
@@ -37,7 +37,7 @@ internal override void Render()
case SymbolName.Blank:
for (int idx = 0; idx < count; idx++)
_rtfWriter.WriteBlank();
- //WriteText wouldnt work if there was a control before.
+ //WriteText wouldn’t work if there was a control before.
break;
case SymbolName.Bullet:
@@ -54,7 +54,7 @@ internal override void Render()
for (int idx = 0; idx < count; idx++)
{
_rtfWriter.WriteControl("u", "8195");
- //I dont know why, but it works:
+ //I don’t know why, but it works:
_rtfWriter.WriteHex(0x20);
}
break;
@@ -63,7 +63,7 @@ internal override void Render()
for (int idx = 0; idx < count; idx++)
{
_rtfWriter.WriteControl("u", "8197");
- //I dont know why, but it works:
+ //I don’t know why, but it works:
_rtfWriter.WriteHex(0x20);
}
break;
@@ -72,7 +72,7 @@ internal override void Render()
for (int idx = 0; idx < count; idx++)
{
_rtfWriter.WriteControl("u", "8194");
- //I dont know why, but it works:
+ //I don’t know why, but it works:
_rtfWriter.WriteHex(0x20);
}
break;
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/ChartRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/ChartRenderer.cs
index 1de31cc2..c64b5e29 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/ChartRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/ChartRenderer.cs
@@ -182,7 +182,7 @@ bool StoreTempImage(string fileName)
// throw new InvalidOperationException("This version of MigraDoc cannot render charts to RTF. Use the WPF build under Windows to create RTF files with charts.");
return false;
-#else
+#else // GDI case here.
try
{
const float resolution = 96;
@@ -192,18 +192,8 @@ bool StoreTempImage(string fileName)
XGraphics gfx =
XGraphics.CreateMeasureContext(new XSize(horzPixels, vertPixels), XGraphicsUnit.Point, XPageDirection.Downwards, new RenderEvents());
#else
-#if GDI
Bitmap bmp = new Bitmap(horzPixels, vertPixels);
XGraphics gfx = XGraphics.FromGraphics(Graphics.FromImage(bmp), new XSize(horzPixels, vertPixels), new RenderEvents());
-#else
- // TODOWPF
- // TODOCORE
- return false;
-#endif
-#if WPF
- // TODOWPF
- XGraphics gfx = null; //XGraphics.FromGraphics(Graphics.FromImage(bmp), new XSize(horzPixels, vertPixels));
-#endif
#endif
//REM: Should not be necessary:
gfx.ScaleTransform(resolution / 72);
@@ -211,10 +201,8 @@ bool StoreTempImage(string fileName)
DocumentRenderer renderer = new MigraDoc.Rendering.DocumentRenderer(_chart.Document!);
renderer.RenderObject(gfx, 0, 0, GetShapeWidth().Point, _chart);
-#if GDI
bmp.SetResolution(resolution, resolution);
bmp.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
-#endif
}
catch (Exception)
{
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/DateFieldRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/DateFieldRenderer.cs
index 87ac24c0..70e0099d 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/DateFieldRenderer.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/DateFieldRenderer.cs
@@ -247,7 +247,7 @@ string TranslateCustomFormatChar(char ch)
///
protected override string GetFieldResult()
{
- return DateTime.Now.ToString(GetEffectiveFormat(_dateField, out _));
+ return DateTimeOffset.Now.ToString(GetEffectiveFormat(_dateField, out _));
}
readonly DateField _dateField;
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/MdRtfMsgs.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/MdRtfMsgs.cs
index c5bfca27..234a965f 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/MdRtfMsgs.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/MdRtfMsgs.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using PdfSharp.Internal;
@@ -48,7 +48,7 @@ internal static MdRtfMsg InvalidNumericFieldFormat(string format)
=> new(MdRtfMsgId.InvalidNumericFieldFormat, $"'{format}' is not a valid format for a numeric field and will be ignored.");
internal static MdRtfMsg CharacterNotAllowedInDateFormat(char character)
- => new(MdRtfMsgId.CharacterNotAllowedInDateFormat, $"The character '{character}' is not allowed in a date fields format string and will be ignored.");
+ => new(MdRtfMsgId.CharacterNotAllowedInDateFormat, $"The character '{character}' is not allowed in a date field’s format string and will be ignored.");
internal static MdRtfMsg UpdateField
=> new(MdRtfMsgId.UpdateField, "< Please update this field. >");
diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs
index fa9f7782..5230386f 100644
--- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs
+++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using System.Diagnostics;
@@ -287,7 +287,7 @@ protected void Translate(string valueName, string rtfCtrl)
}
///
- /// Translates a value named 'valueName' to a Rtf Control word that specifies a Boolean and divides in two control words.
+ /// Translates a value named 'valueName' to a Rtf Control word that specifies a boolean and divides in two control words.
/// If the control word in false case is simply left away, you can also use the Translate function as well.
///
protected void TranslateBool(string valueName, string rtfTrueCtrl, string rtfFalseCtrl, bool withStar)
@@ -357,7 +357,7 @@ protected object GetValueOrDefault(string valName, object valDefault)
}
///
- /// Renders a trailing standard paragraph in case the last element in elements isnt a paragraph.
+ /// Renders a trailing standard paragraph in case the last element in elements isn’t a paragraph.
/// (Some RTF elements need to close with a paragraph.)
///
protected void RenderTrailingParagraph(DocumentElements elements)
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Bullets.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Bullets.cs
index 0af08041..ea72cb54 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Bullets.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Bullets.cs
@@ -58,10 +58,10 @@ public void Create_Bullets()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("HelloWorld");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/bullets/HelloWorld");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -264,10 +264,10 @@ public void BulletLineSpacingTest()
var pdfDocument = pdfRenderer.PdfDocument;
pdfDocument.Options.CompressContentStreams = false;
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("BulletLineSpacingTest");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/bullets/BulletLineSpacingTest");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Helper/DocumentObjectSnapshot.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Helper/DocumentObjectSnapshot.cs
index 194c0bcf..ccb34886 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Helper/DocumentObjectSnapshot.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Helper/DocumentObjectSnapshot.cs
@@ -121,7 +121,7 @@ public DocumentObjectSnapshot(DocumentObject docObj, Dictionary
- net6.0;net8.0;net462
+ $(PDFsharpTargetFrameworks_Tests_Exe)
- CS1685
+ $(NoWarn);1685
True
..\..\..\..\..\StrongnameKey.snk
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/ParagraphTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/ParagraphTests.cs
index dd1f8fbb..b3f4260a 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/ParagraphTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/ParagraphTests.cs
@@ -44,7 +44,7 @@ public void Test_Empty_Paragraph()
var pdfRenderer = new PdfDocumentRenderer { Document = document };
pdfRenderer.RenderDocument();
- var filename = PdfFileUtility.GetTempPdfFileName("Test_Empty_Paragraph");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Empty_Paragraph");
pdfRenderer.PdfDocument.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -62,7 +62,7 @@ public void Test_Empty_FormattedText()
var pdfRenderer = new PdfDocumentRenderer { Document = document };
pdfRenderer.RenderDocument();
- var filename = PdfFileUtility.GetTempPdfFileName("Test_Empty_FormattedText");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Empty_FormattedText");
pdfRenderer.PdfDocument.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -215,7 +215,7 @@ public void Test_Multiline_Border_Paragraph_PageBreaks()
}
// Save sumDoc.
- var filename = PdfFileUtility.GetTempPdfFileName("Test_Multiline_Border_Paragraph_PageBreaks");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Multiline_Border_Paragraph_PageBreaks");
sumDoc.PageLayout = PdfPageLayout.TwoPageLeft;
sumDoc.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
@@ -373,7 +373,7 @@ public void Test_Trailing_Objects_Border_Paragraph_PageBreak()
}
// Save sumDoc.
- var filename = PdfFileUtility.GetTempPdfFileName("Test_Trailing_Objects_Border_Paragraph_PageBreak");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Trailing_Objects_Border_Paragraph_PageBreak");
sumDoc.PageLayout = PdfPageLayout.TwoPageLeft;
sumDoc.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
@@ -469,10 +469,10 @@ public void Test_LineSpacingRule()
var pdfDocument = pdfRenderer.PdfDocument;
pdfDocument.Options.CompressContentStreams = false;
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Test_LineSpacingRule");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_LineSpacingRule");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
@@ -654,10 +654,10 @@ public void Test_PageBreak_And_Fitting_Line_Height()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Test_PageBreak_And_Fitting_Line_Height");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_PageBreak_And_Fitting_Line_Height");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -696,10 +696,66 @@ public void Test_Line_And_PageBreak_Big_Words()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Test_Line_And_PageBreak_Big_Words");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Line_And_PageBreak_Big_Words");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
+ PdfFileUtility.ShowDocumentIfDebugging(filename);
+ }
+
+ [Fact]
+ public void Test_New_Font()
+ {
+ // In this test the font of style "Normal" is replaced by a new font instance without a color been set.
+ // For this reason the ultimately inherited font color is Font.Empty and no text and underline is visible.
+
+ // In the future, a warning could be shown if an empty color is used for drawing an object.
+ // Alternatively or additionally, Colors.Black could automatically been used, if the color is still Colors.Empty after flattening.
+
+ // This test helped to find bugs in PDFsharp 6.2.1, as PdfGraphicsState was not implemented correctly to start with drawing
+ // strokes and fills with alpha channel set to 0, like Color.Empty. The bugs resulted in drawing the content partly in black:
+ // The text was completely visible and the underline until drawing the border. So this test also helps to verify the bugfix.
+
+ var document = new Document();
+
+ // Due to this line the ultimately inherited font color is Font.Empty and no text and underline is visible.
+ document.Styles.Normal.Font = new Font("Arial", 9);
+ document.Styles.Normal.Font.Underline = Underline.Single;
+
+ var section = document.AddSection();
+ section.AddParagraph("Paragraph - underline visible in PDFsharp 6.2.1");
+
+ var tbl1 = section.AddTable();
+ tbl1.AddColumn(Unit.FromCentimeter(10));
+ var row = tbl1.AddRow();
+ row.Cells[0].AddParagraph("Table 1 - underline visible in PDFsharp 6.2.1");
+
+ var tbl2 = section.AddTable();
+ tbl2.AddColumn(Unit.FromCentimeter(10));
+ row = tbl2.AddRow();
+
+ row.Borders.Left = new Border { Width = 0.75, Color = Colors.Black };
+
+ row.Cells[0].AddParagraph("Table 2 With Border - underline still visible in PDFsharp 6.2.1");
+
+ var tbl3 = section.AddTable();
+ tbl3.AddColumn(Unit.FromCentimeter(10));
+ row = tbl3.AddRow();
+ row.Cells[0].AddParagraph("Table 3 - underline not visible in PDFsharp 6.2.1 from now on");
+
+ section.AddParagraph("Paragraph after table - underline not visible");
+
+
+ // Create a renderer for the MigraDoc document.
+ var pdfRenderer = new PdfDocumentRenderer { Document = document };
+
+ // Layout and render document to PDF.
+ pdfRenderer.RenderDocument();
+
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_New_Font");
+ pdfRenderer.PdfDocument.Save(filename);
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/SerializerTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/SerializerTests.cs
index ade87051..97512ffa 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/SerializerTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/SerializerTests.cs
@@ -804,7 +804,7 @@ public void Test_WriteAndReadMdddl_Enum()
var style = doc.Styles[styleName];
style.Should().NotBeNull();
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var desiredValue in Enum.GetValues())
#else
foreach (ParagraphAlignment desiredValue in Enum.GetValues(typeof(ParagraphAlignment)))
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Structs/UnitTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Structs/UnitTests.cs
index 8ea56049..040dc0a5 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Structs/UnitTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Structs/UnitTests.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using FluentAssertions;
@@ -60,7 +60,7 @@ public void Unit_Test()
(unitNull == null).Should().BeTrue();
(unitNull != null).Should().BeFalse();
- // Only check cm variables, which are totally equal because str2Cm with "2cm" wont cause rounding errors.
+ // Only check cm variables, which are totally equal because str2Cm with "2cm" won’t cause rounding errors.
(unit2Cm2 == unit2Cm).Should().BeTrue();
// Use IsSameValue() for other variables.
@@ -69,7 +69,7 @@ public void Unit_Test()
unit2CmPt2.IsSameValue(unit2CmPt).Should().BeTrue();
unit2CmPc2.IsSameValue(unit2CmPc).Should().BeTrue();
- // Only check cm variables, which are totally equal because str2Cm with "2cm" wont cause rounding errors.
+ // Only check cm variables, which are totally equal because str2Cm with "2cm" won’t cause rounding errors.
(unit2Cm2 != unit2Cm).Should().BeFalse();
// Same value - other unit.
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TableTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TableTests.cs
index 902bc046..84ab9189 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TableTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TableTests.cs
@@ -56,10 +56,10 @@ public void Create_Hello_World_TableTests()
rendering.Should().Throw();
- //// Save the document...
+ //// Save the document…
//var filename = PdfFileUtility.GetTempPdfFileName("HelloWorld");
//pdfRenderer.PdfDocument.Save(filename);
- //// ...and start a viewer.
+ //// … and start a viewer.
//PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -126,7 +126,7 @@ public void Test_MergeDown_Simple()
var pdfRenderer = CreateReadablePdfDocumentRenderer(document);
pdfRenderer.RenderDocument();
- var filename = PdfFileUtility.GetTempPdfFileName("Test_MergeDown");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_MergeDown");
pdfRenderer.PdfDocument.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -169,7 +169,7 @@ public void Test_KeepWith_MergeDown_PageBreak()
var pdfRenderer = CreateReadablePdfDocumentRenderer(document);
pdfRenderer.RenderDocument();
- var filename = PdfFileUtility.GetTempPdfFileName("Test_MergeDown_PageBreak");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_MergeDown_PageBreak");
pdfRenderer.PdfDocument.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -207,7 +207,7 @@ public void Test_MergeDown_LineBreak_RowHeight()
var pdfRenderer = CreateReadablePdfDocumentRenderer(document);
pdfRenderer.RenderDocument();
- var filename = PdfFileUtility.GetTempPdfFileName("Test_MergeDown_LineBreak_RowHeight");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_MergeDown_LineBreak_RowHeight");
pdfRenderer.PdfDocument.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
@@ -245,7 +245,7 @@ public void Test_MergeDown_LineBreak_RowHeight()
// Find "ID#2" text object.
var id2Found = streamEnumerator.Text.MoveAndGetNext(x => x.Text == "ID#2", true, out _);
id2Found.Should().BeTrue("text object \"ID#2\" shall be found");
-
+
// Check the following lines drawing the borders for the correct values.
streamEnumerator.MoveNext().Should().BeTrue();
streamEnumerator.Current.Should().Be("ET", "\"ID#1\" shall be the last text of the cell");
@@ -303,7 +303,7 @@ public void Test_Border_Inheritance()
var pdfRenderer = CreateReadablePdfDocumentRenderer(document);
pdfRenderer.RenderDocument();
- var filename = PdfFileUtility.GetTempPdfFileName("Test_Border_Inheritance");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_Border_Inheritance");
pdfRenderer.PdfDocument.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
@@ -311,7 +311,7 @@ public void Test_Border_Inheritance()
var page = pdfRenderer.PdfDocument.Pages[0];
var contentReference = (PdfReference)page.Contents.Elements.Items[0];
var content = (PdfDictionary)contentReference.Value;
- var contentStream = content.Stream.ToString();
+ var contentStream = content.Stream?.ToString() ?? "";
var contentLines = contentStream.Split('\n');
// 1.5 is the desired border width. It shall be set only once.
@@ -388,7 +388,7 @@ public void Test_Huge_MergeDown_Cell()
var pdfRenderer = CreateReadablePdfDocumentRenderer(document);
pdfRenderer.RenderDocument();
- var filename = PdfFileUtility.GetTempPdfFileName("Test_Huge_MergeDown_Cell");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_Huge_MergeDown_Cell");
pdfRenderer.PdfDocument.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -432,7 +432,7 @@ public void Test_Repeated_Heading_Border()
var pdfRenderer = CreateReadablePdfDocumentRenderer(document);
pdfRenderer.RenderDocument();
- var filename = PdfFileUtility.GetTempPdfFileName("Test_Repeated_Heading_Border");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_Repeated_Heading_Border");
pdfRenderer.PdfDocument.Save(filename);
PdfFileUtility.ShowDocumentIfDebugging(filename);
@@ -443,7 +443,7 @@ public void Test_Repeated_Heading_Border()
{
var contentStream = PdfFileHelper.GetPageContentStream(pdfRenderer.PdfDocument, pageIdx);
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
// Split ContentStream where the "Row" text is rendered.
var contentByRows = contentStream.Split("(Row) Tj");
contentByRows.Length.Should().Be(3, "as \"Row\" occurs twice per page, the stream should be split into 3 parts");
@@ -474,7 +474,7 @@ public void Test_Repeated_Heading_Border()
bottomBorderDrawLinePartLines.Should().NotContain(contentStreamBottomWidth, "heading bottom border should not be of content bottom border width");
bottomBorderDrawLinePartLines.Should().NotContain(contentStreamBottomColor, "heading bottom border should not be of content bottom border color");
-
+
// Row 1.
contentRowDrawLineParts = rowsByDrawLinesByLines[1];
contentRowDrawLineParts.Length.Should().Be(3, "for the content rows one bottom and one top border should split the content into 3 parts");
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Template.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Template.cs
index cba7410f..23fef44b 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Template.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Template.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using MigraDoc.DocumentObjectModel.Fields;
@@ -52,10 +52,10 @@ public void Create_Hello_World_TemplateDOM()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("HelloWorld");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/HelloWorld");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TextFrames.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TextFrames.cs
index e68470e2..e392b9f9 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TextFrames.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TextFrames.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using PdfSharp.Fonts;
@@ -56,10 +56,10 @@ public void Create_TextFrames()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Create_TextFrames");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Create_TextFrames");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/valuemodel/BasicTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/valuemodel/BasicTests.cs
index e200fd1b..f9b83d91 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/valuemodel/BasicTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/valuemodel/BasicTests.cs
@@ -19,7 +19,7 @@ public Test1Object()
}
internal override void Serialize(Serializer serializer)
- => throw new NotImplementedException();
+ => throw new NotSupportedException();
internal override Meta Meta => TheMeta;
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj
index 52ee0ccf..7d71bf5f 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0;net8.0;net462
+ $(PDFsharpTargetFrameworks_Tests_Exe)
MigraDoc.GBE_Runner
MigraDoc.GBE_Runner.Program
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj
index b25f466e..e1590221 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj
@@ -1,7 +1,7 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Tests_Windows)
MigraDoc.GrammarByExample_GDI
true
true
@@ -92,10 +92,4 @@
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj
index ec74ce24..b47688c0 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj
@@ -1,7 +1,7 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Tests_Windows)
MigraDoc.GrammarByExample_WPF
true
true
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj
index b69b50dc..da8c0f2c 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj
@@ -1,7 +1,7 @@
- net6.0;net8.0;net462
+ $(PDFsharpTargetFrameworks_Tests_Exe)
false
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/DdlGbeTestBase.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/DdlGbeTestBase.cs
index 01eff602..d87263cc 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/DdlGbeTestBase.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/DdlGbeTestBase.cs
@@ -3,7 +3,7 @@
using System;
using System.Diagnostics;
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
#endif
using MigraDoc.DocumentObjectModel;
@@ -106,7 +106,7 @@ void CompatibilityPatchCallback(Document document)
style!.Font.Name = "Verdana";
#endif
#if CORE
- // Note: Core uses SnippetsFontResolver and all required fonts should be available.
+ // Core uses SnippetsFontResolver and all required fonts should be available.
var style = document.Styles[Style.DefaultParagraphName];
Debug.Assert(style != null, nameof(style) + " != null");
// Since all reference documents created with PDFsharp 1.40 or earlier use Verdana, we change the default to Verdana here for all DLL snippets.
@@ -116,7 +116,7 @@ void CompatibilityPatchCallback(Document document)
internal static string WslPathHack(string path)
{
-#if !NET6_0_OR_GREATER
+#if !NET8_0_OR_GREATER
// .NET 4.6.2 or .NETStandard 2.0, for Windows only.
return path;
#else
@@ -130,7 +130,7 @@ internal static string WslPathHack(string path)
return path.Replace(@"D:\", "/mnt/c/").Replace('\\', '/');
}
- throw new NotImplementedException($"Platform {Environment.OSVersion} not yet supported.");
+ throw new NotSupportedException($"Platform {Environment.OSVersion} not yet supported.");
#endif
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/TestContext.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/TestContext.cs
index d348e33e..f8dfbdb6 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/TestContext.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/TestContext.cs
@@ -21,7 +21,7 @@ public TestContext()
var folder0 = @"D:\GBE-Output\";
Directory.CreateDirectory(DdlGbeTestBase.WslPathHack(folder0));
- var now = DateTime.Now;
+ var now = DateTimeOffset.Now;
//Console.WriteLine($"Now {now})");
#if GBE
const string tag = "GBE";
@@ -32,12 +32,24 @@ public TestContext()
#elif WPF
const string tag = "WPF";
#endif
-#if NET8_0
+#if NET462
+ const string tag2 = "NET462";
+#elif NET12_0
+ const string tag2 = "NET120";
+#elif NET11_0
+ const string tag2 = "NET110";
+#elif NET10_0
+ const string tag2 = "NET100";
+#elif NET9_0
+ const string tag2 = "NET90";
+#elif NET8_0
const string tag2 = "NET80";
+#elif NET7_0
+ const string tag2 = "NET70";
#elif NET6_0
const string tag2 = "NET60";
-#elif NET462
- const string tag2 = "NET462";
+#elif NET5_0
+ const string tag2 = "NET50";
#elif NETSTANDARD2_0
const string tag2 = "NETstandard20";
#else
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/VisualComparisonTestBase.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/VisualComparisonTestBase.cs
index f632bd75..785f8ce7 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/VisualComparisonTestBase.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/VisualComparisonTestBase.cs
@@ -68,7 +68,7 @@ public string CreatePdfFromMdddlFile(string pdfFile, string mdddlPath, string te
var document = DdlReaderDocumentFromFile(file);
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
#if CORE
document.Info.Author += "[.NET 6.0+ Core build]";
#elif GDI
@@ -102,7 +102,7 @@ public string CreatePdfFromDdlString(string pdfFile, string ddlString, string te
Document DdlReaderDocumentFromFile(string file)
{
- //#if NET6_0_OR_GREATER
+ //#if NET8_0_OR_GREATER
// var ansiEncoding = CodePagesEncodingProvider.Instance.GetEncoding(1252)!;
//#else
// var ansiEncoding = Encoding.GetEncoding(1252);
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj
index 1fe83cdd..842b3c0e 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj
@@ -1,7 +1,7 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Tests_Windows)
true
True
..\..\..\..\..\StrongnameKey.snk
@@ -22,7 +22,8 @@
-
+
+
@@ -32,7 +33,8 @@
-
+
+
@@ -42,6 +44,7 @@
+
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj
index b53d9512..a4587e2a 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj
@@ -1,7 +1,7 @@
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Tests_Windows)
true
true
True
@@ -23,7 +23,8 @@
-
+
+
@@ -33,7 +34,8 @@
-
+
+
@@ -43,6 +45,7 @@
+
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ChartTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ChartTests.cs
index 71835899..1a77faa5 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ChartTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ChartTests.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using PdfSharp.TestHelper;
@@ -55,10 +55,10 @@ public void Create_MigraDoc_Chart_Test()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("ChartTests");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/charts/ChartTests");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
#if DEBUG___
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs
index 9d73c186..2916ca7d 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using System.Globalization;
@@ -50,10 +50,10 @@ public void DateTimeTest()
var englishMonths = new[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
var amPm = new[] { "AM", "PM" };
var germanWeekdays = new[] { "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag" };
- var germanMonths = new[] { "Januar", "Februar", "Mrz", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" };
+ var germanMonths = new[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" };
// filenamePattern with placeholder for cultureInfo.
- var filenamePattern = IOUtility.GetTempFileName("DateTime{0}", "pdf");
+ var filenamePattern = IOUtility.GetTempFullFileName("DateTime{0}", "pdf");
// Create one result file per cultureInfo.
var cultureInfos = new[] { null, CultureInfo.GetCultureInfo("en-us"), CultureInfo.GetCultureInfo("de-de") };
@@ -63,9 +63,9 @@ public void DateTimeTest()
var section = document.AddSection();
- section.AddParagraph($"123456: {new DateTime(1, 2, 3, 4, 5, 6).ToString(cultureInfo)}");
+ section.AddParagraph($"123456: {new DateTimeOffset(1, 2, 3, 4, 5, 6,TimeSpan.Zero).ToString(cultureInfo)}");
- section.AddParagraph($"DateTime.Now: {DateTime.Now.ToString(cultureInfo)}");
+ section.AddParagraph($"DateTimeOffset.Now: {DateTimeOffset.Now.ToString(cultureInfo)}");
var p = section.AddParagraph("p.AddDateField(): ");
p.AddDateField();
@@ -86,7 +86,7 @@ public void DateTimeTest()
// Check PDF file content.
var streamEnumerator = PdfFileHelper.GetPageContentStreamEnumerator(pdfDocument, 0);
- // Get all text content split by whitespace.
+ // Get all text content split by white-space.
var texts = new List();
while (streamEnumerator.Text.MoveAndGetNext(true, out var textInfo))
texts.AddRange(textInfo!.Text.Split(' '));
@@ -98,19 +98,21 @@ public void DateTimeTest()
if (isEnUs)
{
- texts.Count.Should().Be(19);
+ texts.Count.Should().Be(21);
- // 123456: 2/3/0001 4:05:06 AM
+ // 123456: 2/3/0001 4:05:06 AM +00:00
texts[++idx].Should().Be("123456:");
Regex.IsMatch(texts[++idx], "[\\d]{1,2}/[\\d]{1,2}/[\\d]{4}").Should().BeTrue();
Regex.IsMatch(texts[++idx], "[\\d]{1,2}:[\\d]{2}:[\\d]{2}").Should().BeTrue();
amPm.Should().Contain(texts[++idx]);
+ Regex.IsMatch(texts[++idx], "[+-][\\d]{2}:[\\d]{2}").Should().BeTrue();
- // DateTime.Now: 10/14/2024 10:40:50 AM
- texts[++idx].Should().Be("DateTime.Now:");
+ // DateTimeOffset.Now: 10/14/2024 10:40:50 AM +02:00
+ texts[++idx].Should().Be("DateTimeOffset.Now:");
Regex.IsMatch(texts[++idx], "[\\d]{1,2}/[\\d]{1,2}/[\\d]{4}").Should().BeTrue();
Regex.IsMatch(texts[++idx], "[\\d]{1,2}:[\\d]{2}:[\\d]{2}").Should().BeTrue();
amPm.Should().Contain(texts[++idx]);
+ Regex.IsMatch(texts[++idx], "[+-][\\d]{2}:[\\d]{2}").Should().BeTrue();
// p.AddDateField(): 10/14/2024 10:40:50 AM
texts[++idx].Should().Be("p.AddDateField\\(\\):");
@@ -129,17 +131,19 @@ public void DateTimeTest()
}
else if (isDeDe)
{
- texts.Count.Should().Be(15);
+ texts.Count.Should().Be(17);
// 123456: 03.02.0001 04:05:06
texts[++idx].Should().Be("123456:");
Regex.IsMatch(texts[++idx], "[\\d]{2}.[\\d]{2}.[\\d]{4}").Should().BeTrue();
Regex.IsMatch(texts[++idx], "[\\d]{2}:[\\d]{2}:[\\d]{2}").Should().BeTrue();
+ Regex.IsMatch(texts[++idx], "[+-][\\d]{2}:[\\d]{2}").Should().BeTrue();
- // DateTime.Now: 14.10.2024 10:40:50
- texts[++idx].Should().Be("DateTime.Now:");
+ // DateTimeOffset.Now: 14.10.2024 10:40:50
+ texts[++idx].Should().Be("DateTimeOffset.Now:");
Regex.IsMatch(texts[++idx], "[\\d]{2}.[\\d]{2}.[\\d]{4}").Should().BeTrue();
Regex.IsMatch(texts[++idx], "[\\d]{2}:[\\d]{2}:[\\d]{2}").Should().BeTrue();
+ Regex.IsMatch(texts[++idx], "[+-][\\d]{2}:[\\d]{2}").Should().BeTrue();
// p.AddDateField(): 14.10.2024 10:40:50
texts[++idx].Should().Be("p.AddDateField\\(\\):");
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Experimental/CreateOnRequest.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Experimental/CreateOnRequest.cs
index 90bb48b5..811a4fa7 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Experimental/CreateOnRequest.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Experimental/CreateOnRequest.cs
@@ -87,12 +87,12 @@ public override bool Equals(object? o)
return false;
}
- static Boolean ISectionInterface.operator ==(Section? c1, Section? c2)
+ static bool ISectionInterface.operator ==(Section? c1, Section? c2)
{
return c2 is null;
}
- static Boolean ISectionInterface.operator !=(Section? c1, Section? c2)
+ static bool ISectionInterface.operator !=(Section? c1, Section? c2)
{
return true;
}
@@ -172,36 +172,36 @@ public override string ToString()
return stringBuilder.ToString();
}
- static Boolean ISectionInterface.operator ==(Section? c1, Section? c2)
+ static bool ISectionInterface.operator ==(Section? c1, Section? c2)
{
Console.WriteLine("==");
return true;
}
- static Boolean ISectionInterface.operator !=(Section? c1, Section? c2)
+ static bool ISectionInterface.operator !=(Section? c1, Section? c2)
{
Console.WriteLine("!=");
return true;
}
- public static Boolean operator ==(Section? c1, Section? c2)
+ public static bool operator ==(Section? c1, Section? c2)
{
return true;
}
- public static Boolean operator !=(Section? c1, Section? c2)
+ public static bool operator !=(Section? c1, Section? c2)
{
return true;
}
- //static Boolean SectionInterface.operator ==(SectionInterface c1, SectionInterface c2)
+ //static bool SectionInterface.operator ==(SectionInterface c1, SectionInterface c2)
//{
// if (c2 is null)
// return true;
// return false;
//}
- //static Boolean SectionInterface.operator !=(SectionInterface c1, SectionInterface c2)
+ //static bool SectionInterface.operator !=(SectionInterface c1, SectionInterface c2)
//{
// return !(c1 == c2);
//}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs
index 209a0881..8e7bc4aa 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs
@@ -1,4 +1,7 @@
-using System;
+// MigraDoc - Creating Documents on the Fly
+// See the LICENSE file in the solution root for more information.
+
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -18,8 +21,14 @@ public static class SkippableTests
/// True if slow tests should be skipped.
public static bool SkipSlowTests()
{
+#if RUN_SLOW_TESTS
+ return false;
+#else
var env = Environment.GetEnvironmentVariable("PDFsharpTests");
+ //#/warning slow tests
+ // env = "x";
return String.IsNullOrEmpty(env);
+#endif
}
///
@@ -29,7 +38,7 @@ public static bool SkipSlowTests()
/// True if slow tests should be skipped.
public static bool SkipSlowTestsUnderDotNetFramework()
{
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
return false;
#else
return SkipSlowTests();
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs
index 7ce3ca3c..93fab501 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs
@@ -39,14 +39,14 @@ public void Use_default_error_font()
// In GDI and WPF builds rendering a document must succeed if the error font is available - even is an error message is rendered.
var document = CreateDocumentWithErrorMessage();
- var filename = PdfFileUtility.GetTempPdfFileName("Use_default_error_font");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/fonts/Use_default_error_font");
// Even with error message the document must be successfully created.
var pdfDocument = RenderDocumentShouldSucceed(document, filename);
CheckErrorFont(pdfDocument, "Courier New");
#else
- throw new NotImplementedException("Unknown flavor.");
+ throw new NotSupportedException("Unknown flavor.");
#endif
}
finally
@@ -77,14 +77,14 @@ public void Use_alternate_error_font()
// In GDI and WPF builds rendering a document must succeed if the error font is available - even is an error message is rendered.
var document = CreateDocumentWithErrorMessage();
- var filename = PdfFileUtility.GetTempPdfFileName("Use_alternate_error_font");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/fonts/Use_alternate_error_font");
// Even with error message the document must be successfully created.
var pdfDocument = RenderDocumentShouldSucceed(document, filename);
CheckErrorFont(pdfDocument, "Segoe UI");
#else
- throw new NotImplementedException("Unknown flavor.");
+ throw new NotSupportedException("Unknown flavor.");
#endif
}
finally
@@ -137,14 +137,14 @@ public void Use_default_bullets_font()
// In GDI and WPF builds rendering a document with bullet lists must succeed if the bullet fonts are available.
var document = CreateDocumentWithBulletList();
- var filename = PdfFileUtility.GetTempPdfFileName("Use_default_bullets_font");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/fonts/Use_default_bullets_font");
// The document must be successfully created.
var pdfDocument = RenderDocumentShouldSucceed(document, filename);
CheckBulletFonts(pdfDocument, "Courier New", "Courier New", "Courier New");
#else
- throw new NotImplementedException("Unknown flavor.");
+ throw new NotSupportedException("Unknown flavor.");
#endif
}
finally
@@ -175,14 +175,14 @@ public void Use_alternate_bullets_font()
// In GDI and WPF builds rendering a document with bullet lists must succeed if the bullet fonts are available.
var document = CreateDocumentWithBulletList();
- var filename = PdfFileUtility.GetTempPdfFileName("Use_alternate_bullets_font");
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/fonts/Use_alternate_bullets_font");
// The document must be successfully created.
var pdfDocument = RenderDocumentShouldSucceed(document, filename);
CheckBulletFonts(pdfDocument, "Arial", "Times new roman", "Verdana");
#else
- throw new NotImplementedException("Unknown flavor.");
+ throw new NotSupportedException("Unknown flavor.");
#endif
}
finally
@@ -254,9 +254,9 @@ PdfDocument RenderDocumentShouldSucceed(Document document, string filename)
var pdfDocument = pdfRenderer.PdfDocument;
- // Save the document...
+ // Save the document…
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
return pdfDocument;
@@ -267,8 +267,9 @@ void CheckErrorFont(PdfDocument pdfDocument, string expectedFontName)
// Check font of the error message.
var fontName = PdfFileHelper.GetCurrentFontName(0, pdfDocument, streamEnumerator =>
{
- // Move to "Image '???' not found.".
- streamEnumerator.Text.MoveAndGetNext(x => x.Text == "Image '???' not found.", true, out _).Should().BeTrue();
+ // Move to "Image '' not found.".
+ // Move to "Image 'c:/not-existing-image' not found.".
+ streamEnumerator.Text.MoveAndGetNext(x => x.Text == "Image 'c:/not-existing-image' not found.", true, out _).Should().BeTrue();
});
fontName.ToLower().Should().Be(expectedFontName.ToLower());
}
@@ -340,7 +341,7 @@ static Document CreateDocumentWithErrorMessage()
var section = document.AddSection();
// Add a not existing image to force error message.
- section.AddImage("not-existing-image");
+ section.AddImage("c:/not-existing-image");
return document;
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Helper/SecurityTestHelper.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Helper/SecurityTestHelper.cs
index aa1f3bae..177a26c3 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Helper/SecurityTestHelper.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Helper/SecurityTestHelper.cs
@@ -3,6 +3,8 @@
using MigraDoc.DocumentObjectModel;
using MigraDoc.Rendering;
+using PdfSharp.Quality;
+using System.IO;
using static PdfSharp.TestHelper.SecurityTestHelper;
namespace MigraDoc.Tests.Helper
@@ -52,39 +54,41 @@ public static PdfDocumentRenderer RenderSecuredStandardTestDocument(TestOptions
return RenderSecuredDocument(CreateStandardTestDocument(), options);
}
- public static void WriteStandardTestDocument(string filename)
+ public static void WriteStandardTestDocument(Stream stream)
{
var pdfRenderer = RenderDocument(CreateStandardTestDocument());
- pdfRenderer.Save(filename);
+ pdfRenderer.Save(stream, false);
}
- public static string GetStandardTestDocument()
+ public static Stream GetStandardTestDocument()
{
- const string filename = "temp.pdf";
- WriteStandardTestDocument(filename);
- return filename;
+ var stream = CreateTempStream("unittests/SecurityTests/StandardTestDocument");
+ WriteStandardTestDocument(stream);
+ return stream;
}
- public static void WriteSecuredStandardTestDocument(string filename, TestOptions options)
+ public static void WriteSecuredStandardTestDocument(Stream stream, TestOptions options)
{
var pdfRenderer = RenderSecuredStandardTestDocument(options);
- pdfRenderer.Save(filename);
+ pdfRenderer.Save(stream, false);
}
- public static string GetSecuredStandardTestDocument(TestOptions options)
+ public static Stream GetSecuredStandardTestDocument(TestOptions options)
{
- string filename;
+ Stream stream;
if (options.Encryption == TestEncryptions.V5R5ReadOnly)
{
- GetAssetsTestFile(options, out filename);
+ // Open file from Assets.
+ GetAssetsTestFile(options, out var filename);
+ stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
}
else
{
// Render secured test document.
- filename = "temp.pdf";
- WriteSecuredStandardTestDocument(filename, options);
+ stream = CreateTempStream("unittests/SecurityTests/SecuredStandardTestDocument");
+ WriteSecuredStandardTestDocument(stream, options);
}
- return filename;
+ return stream;
}
}
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormats.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormatsTests.cs
similarity index 87%
rename from src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormats.cs
rename to src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormatsTests.cs
index 437bafc7..28546fc6 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormats.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormatsTests.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using System.Runtime.InteropServices;
@@ -24,9 +24,9 @@
namespace MigraDoc.Tests
{
[Collection("PDFsharp")]
- public class ImageFormats : IDisposable
+ public class ImageFormatsTests : IDisposable
{
- public ImageFormats()
+ public ImageFormatsTests()
{
PdfSharpCore.ResetAll();
#if CORE
@@ -61,10 +61,10 @@ public void Test_Image_Formats()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Image_Formats");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/images/Image_Formats");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -100,10 +100,10 @@ public void Test_Image_BASE64()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Image_Base64");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/images/Image_Base64");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -185,10 +185,10 @@ public void Test_Image_Interpolate()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Image_Interpolate");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/images/Image_Interpolate");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -308,8 +308,70 @@ struct TestImage
public Unit? Width { get; set; }
}
+#if true_
+#warning Deactivate this before publishing.
+
+ readonly TestImage[] _testImages =
+ [
+ // Test individual images.
+
+ //// JPEG
+ //new() { Path = @"jpeg\windows7problem.jpg", Comment = "JPEG image", Width = "12cm" },
+ //new() { Path = @"jpeg\TruecolorNoAlpha.jpg", Comment = "JPEG image" },
+ //new() { Path = @"jpeg\truecolorA.jpg", Comment = "JPEG image" },
+ //new() { Path = @"jpeg\PowerBooks_CMYK.jpg", Comment = "JPEG with EXIF header" },
+ //new() { Path = @"jpeg\indexedmonoA.jpg", Comment = "JPEG image" },
+ //new() { Path = @"jpeg\grayscaleNoAlpha.jpg", Comment = "JPEG image" },
+ //new() { Path = @"jpeg\grayscaleA.jpg", Comment = "JPEG image" },
+ //new() { Path = @"jpeg\color8A.jpg", Comment = "JPEG image" },
+ //new() { Path = @"jpeg\color4A.jpg", Comment = "JPEG image" },
+ //new() { Path = @"jpeg\blackwhiteA.jpg", Comment = "JPEG image" },
+ //new() { Path = @"jpeg\Balloons_CMYK.jpg", Comment = "CMYK" },
+ //new() { Path = @"jpeg\Balloons_CMYK - Copy.jpg", Comment = "CMYK?" },
+
+ //// BMP
+ //new() { Path = @"bmp\BlackwhiteA.bmp", Comment = "BMP image" },
+ //new() { Path = @"bmp\BlackwhiteA2.bmp", Comment = "BMP image" },
+ //new() { Path = @"bmp\BlackwhiteTXT.bmp", Comment = "BMP image", Width = "8cm" },
+ new() { Path = @"bmp\Color4A.bmp", Comment = "BMP image" },
+ //new() { Path = @"bmp\Color8A.bmp", Comment = "BMP image" },
+ //new() { Path = @"bmp\GrayscaleA.bmp", Comment = "BMP image" },
+ //new() { Path = @"bmp\IndexedmonoA.bmp", Comment = "BMP image" },
+ //new() { Path = @"bmp\Test_OS2.bmp", Comment = "OS/2 bitmap, not supported by Core build" },
+ //new() { Path = @"bmp\TruecolorA.bmp", Comment = "BMP image" },
+ //new() { Path = @"bmp\TruecolorMSPaint.bmp", Comment = "BMP image" },
+
+ //// PNG
+ //new() { Path = @"png\windows7problem.png", Comment = "PNG", Width = "12cm" },
+ //new() { Path = @"png\truecolorAlpha.png", Comment = "PNG" },
+ //new() { Path = @"png\truecolorA.png", Comment = "PNG" },
+ //new() { Path = @"png\indexedmonoA.png", Comment = "PNG" },
+ //new() { Path = @"png\grayscaleAlpha.png", Comment = "PNG" },
+ //new() { Path = @"png\grayscaleA.png", Comment = "PNG" },
+ //new() { Path = @"png\color8A.png", Comment = "PNG" },
+ //new() { Path = @"png\color4A.png", Comment = "PNG" },
+ //new() { Path = @"png\blackwhiteA.png", Comment = "PNG" },
+
+ //// BMP & PNG
+ //new() { Path = @"MigraDoc.bmp", Comment = "BMP image", Width = "8cm" },
+ //new() { Path = @"Logo landscape.bmp", Comment = "BMP", Width = "12cm" },
+ //new() { Path = @"Logo landscape MS Paint.bmp", Comment = "BMP", Width = "12cm" },
+ //new() { Path = @"Logo landscape 256.bmp", Comment = "BMP image", Width = "12cm" },
+ //new() { Path = @"MigraDoc.png", Comment = "PNG image", Width = "8cm" },
+ //new() { Path = @"Logo landscape.png", Comment = "PNG image", Width = "12cm" },
+ //new() { Path = @"Logo landscape 256.png", Comment = "PNG image", Width = "12cm" },
+
+ //// GIF, TIFF, PNG
+ //new() { Path = @"misc\image009.gif", Comment = "GIF, not supported by Core build" },
+ //new() { Path = @"misc\Rose (RGB 8).tif", Comment = "TIFF, not supported by Core build" },
+ //new() { Path = @"misc\Test.gif", Comment = "GIF, not supported by Core build" },
+ //new() { Path = @"misc\Test.png", Comment = "PNG image" }
+ ];
+#else
readonly TestImage[] _testImages =
[
+ // Test all images.
+
// JPEG
new() { Path = @"jpeg\windows7problem.jpg", Comment = "JPEG image", Width = "12cm" },
new() { Path = @"jpeg\TruecolorNoAlpha.jpg", Comment = "JPEG image" },
@@ -362,5 +424,6 @@ struct TestImage
new() { Path = @"misc\Test.gif", Comment = "GIF, not supported by Core build" },
new() { Path = @"misc\Test.png", Comment = "PNG image" }
];
+#endif
}
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MemoryTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MemoryTests.cs
new file mode 100644
index 00000000..381226de
--- /dev/null
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MemoryTests.cs
@@ -0,0 +1,119 @@
+// MigraDoc - Creating Documents on the Fly
+// See the LICENSE file in the solution root for more information.
+
+using MigraDoc.DocumentObjectModel;
+using MigraDoc.DocumentObjectModel.Fields;
+using MigraDoc.Rendering;
+using PdfSharp.Diagnostics;
+#if CORE
+using PdfSharp.Fonts;
+#endif
+using PdfSharp.Pdf;
+using PdfSharp.Quality;
+using Xunit;
+using FluentAssertions;
+
+namespace MigraDoc.Tests
+{
+ [Collection("PDFsharp")]
+ public class MemoryTests : IDisposable
+ {
+ public MemoryTests()
+ {
+ PdfSharpCore.ResetAll();
+#if CORE
+ GlobalFontSettings.FontResolver = new UnitTestFontResolver();
+#endif
+ }
+
+ public void Dispose()
+ {
+ PdfSharpCore.ResetAll();
+ }
+
+ [Fact]
+ public void MigraDoc_MemoryLeak_Test()
+ {
+ // Background information:
+ // We had a bug that a variable used for caching caused
+ // a MigraDoc Document object to remain in memory.
+ // This test creates a MigraDoc Document, renders a PDF,
+ // and finally assures that Document, PdfRenderer, and PdfDocument
+ // are disposed after a garbage collection.
+
+ // Create weak references for testing.
+ WeakReference documentReference;
+ WeakReference pdfDocumentReference;
+ WeakReference pdfDocumentRendererReference;
+
+ // Create a PDF file and assign weak references.
+ MyCreateDocument();
+
+ // All weak references refer to objects that are out of scope here.
+
+ // Wait for Garbage Collection.
+ //GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ GC.WaitForFullGCComplete();
+
+ // Check that all weak references are invalid now.
+ documentReference.TryGetTarget(out _).Should().BeFalse();
+ pdfDocumentReference.TryGetTarget(out _).Should().BeFalse();
+ pdfDocumentRendererReference.TryGetTarget(out _).Should().BeFalse();
+ return;
+
+ // Create a PDF file and assign weak references.
+ void MyCreateDocument()
+ {
+ // Create a MigraDoc document.
+ var document = new Document();
+ documentReference = new(document);
+
+ // Add a section to the document.
+ var section = document.AddSection();
+
+ // Add a paragraph to the section.
+ var paragraph = section.AddParagraph();
+
+ // Set font color.
+ //paragraph.Format.Font.Color = Color.FromCmyk(100, 30, 20, 50);
+ paragraph.Format.Font.Color = Colors.DarkBlue;
+
+ // Add some text to the paragraph.
+ paragraph.AddFormattedText("Hello, World!", TextFormat.Bold);
+
+ // Create the primary footer.
+ var footer = section.Footers.Primary;
+
+ // Add content to footer.
+ paragraph = footer.AddParagraph();
+ paragraph.Add(new DateField { Format = "yyyy/MM/dd HH:mm:ss" });
+ paragraph.Format.Alignment = ParagraphAlignment.Center;
+
+ // Create a renderer for the MigraDoc document.
+ var pdfRenderer = new PdfDocumentRenderer()
+ {
+ // Associate the MigraDoc document with a renderer.
+ Document = document
+ };
+ pdfDocumentRendererReference = new(pdfRenderer);
+
+ // Layout and render document to PDF.
+ pdfRenderer.RenderDocument();
+ pdfDocumentReference = new WeakReference(pdfRenderer.PdfDocument);
+
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/MigraDocMemoryLeakTest");
+ pdfRenderer.PdfDocument.Save(filename);
+ // … and start a viewer.
+ PdfFileUtility.ShowDocumentIfDebugging(filename);
+
+ // Check that all weak references are valid now.
+ documentReference.TryGetTarget(out _).Should().BeTrue();
+ pdfDocumentReference.TryGetTarget(out _).Should().BeTrue();
+ pdfDocumentRendererReference.TryGetTarget(out _).Should().BeTrue();
+ }
+ }
+ }
+}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj
index 60f0258a..d9c11e09 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj
@@ -1,7 +1,7 @@
- net6.0;net8.0;net462
+ $(PDFsharpTargetFrameworks_Tests_Exe)
True
..\..\..\..\..\StrongnameKey.snk
@@ -21,7 +21,8 @@
-
+
+
@@ -30,6 +31,7 @@
+
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MultipleFooters.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MultipleFooters.cs
index 15c42bf0..f6777ccc 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MultipleFooters.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MultipleFooters.cs
@@ -53,10 +53,10 @@ public void Create_Multiple_Footers()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Multiple_Footers");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/footers/Multiple_Footers");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs
index b3c41387..d30ea7d4 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs
@@ -1,4 +1,7 @@
-using System;
+// MigraDoc - Creating Documents on the Fly
+// See the LICENSE file in the solution root for more information.
+
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -47,14 +50,14 @@ public void Test_Page_Formats()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Page_Formats");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/pages/Page_Formats");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
- private Document CreateDocument()
+ Document CreateDocument()
{
var text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore " +
"et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " +
@@ -286,10 +289,10 @@ public void Test_Page_Formats_Inheritance()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("Page_Formats_Inheritance");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/pages/Page_Formats_Inheritance");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/RtfRendererTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/RtfRendererTests.cs
index ae125bdd..2c00c08f 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/RtfRendererTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/RtfRendererTests.cs
@@ -20,7 +20,7 @@
#if WPF
using System.IO;
#endif
-#if !NET6_0_OR_GREATER
+#if !NET8_0_OR_GREATER
using PdfSharp.TestHelper;
#endif
@@ -68,8 +68,8 @@ public void Create_Hello_World_RtfRendererTests()
// Create a renderer for the MigraDoc document.
var rtfRenderer = new RtfDocumentRenderer();
- // Save the document...
- var filename = IOUtility.GetTempFileName("HelloWorld", null);
+ // Save the document…
+ var filename = IOUtility.GetTempFullFileName("HelloWorld", null);
#if DEBUG___
MigraDoc.DocumentObjectModel.IO.DdlWriter dw = new MigraDoc.DocumentObjectModel.IO.DdlWriter(filename + "_0.mdddl");
@@ -85,7 +85,7 @@ public void Create_Hello_World_RtfRendererTests()
dw.WriteDocument(document);
dw.Close();
#endif
- //// ...and start a viewer.
+ //// … and start a viewer.
//PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -150,9 +150,9 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
// Description:
section.AddParagraph("This document contains several tabstop test cases. For each tabstop a tab is used to reach it.\n" +
- $"By default the value \"{valueStr}\" is used for the content that should be aligned by the tabstops.\n" +
+ $"By default the value '{valueStr}' is used for the content that should be aligned by the tabstops.\n" +
"For the special cases where an additional left aligned tabstop at position 0 is required for correct RTF generation, " +
- $"the value \"{valueUnifyTabStopsStr}\" is used instead.\n" +
+ $"the value '{valueUnifyTabStopsStr}' is used instead.\n" +
"This special case occurs, if a single decimal tabstop is used in a table rendered in RTF." +
"In that case RTF requires no tab to reach the tabstop.");
@@ -160,7 +160,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
var paragraph = section.AddParagraph("Alignment order");
paragraph.Style = StyleNames.Heading1;
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
section.AddParagraph(alignment.ToString());
#else
@@ -173,7 +173,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
paragraph.Style = StyleNames.Heading1;
TestHelper.DrawHorizontalPosition(section, position1);
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
#else
foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment)))
@@ -193,7 +193,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
paragraph.Style = StyleNames.Heading1;
TestHelper.DrawHorizontalPosition(section, position1, position2);
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
#else
foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment)))
@@ -222,7 +222,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
var table = section.AddTable();
table.AddColumn(Unit.FromCentimeter(16));
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
#else
foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment)))
@@ -261,7 +261,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
table.AddColumn(Unit.FromCentimeter(2));
table.AddColumn(Unit.FromCentimeter(14));
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
#else
foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment)))
@@ -299,7 +299,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
table = section.AddTable();
table.AddColumn(Unit.FromCentimeter(16));
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
#else
foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment)))
@@ -331,7 +331,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
table = section.AddTable();
table.AddColumn(Unit.FromCentimeter(16));
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
#else
foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment)))
@@ -379,7 +379,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
table = section.AddTable();
table.AddColumn(Unit.FromCentimeter(16));
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
#else
foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment)))
@@ -417,7 +417,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
table = section.AddTable();
table.AddColumn(Unit.FromCentimeter(16));
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
foreach (var alignment in Enum.GetValues())
#else
foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment)))
@@ -448,10 +448,10 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
TestHelper.DrawHorizontalPosition(section, position1);
// Render PDF and RTF files.
- var filename = "Test_Tabs_";
+ var filename = "unittests/migradoc/text/Test_Tabs_";
filename += Capabilities.BackwardCompatibility.DoNotUnifyTabStopHandling ? "DoNotUnifyTabStopHandling" : "UnifyTabStopHandling";
- var pdfFilename = PdfFileUtility.GetTempPdfFileName(filename);
+ var pdfFilename = PdfFileUtility.GetTempPdfFullFileName(filename);
var rtfFilename = pdfFilename.Replace(".pdf", ".rtf");
var rtfRenderer = new RtfDocumentRenderer();
@@ -482,11 +482,11 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
{
// Get a position of valueUnifyTabStopsStr.
valuePos = rtf.IndexOf(valueUnifyTabStopsStr, searchPos, StringComparison.Ordinal);
- valuePos.Should().BeGreaterThan(0, $"\"{valueUnifyTabStopsStr}\" should occur 5 times in the RTF file after the description text.");
+ valuePos.Should().BeGreaterThan(0, $"'{valueUnifyTabStopsStr}' should occur 5 times in the RTF file after the description text.");
// Get the position of the cell containing the value.
var cellPos = rtf.LastIndexOf(cellStr, valuePos, StringComparison.Ordinal);
- cellPos.Should().BeGreaterThan(0, $"\"{valueUnifyTabStopsStr}\" should occur inside of a cell.");
+ cellPos.Should().BeGreaterThan(0, $"'{valueUnifyTabStopsStr}' should occur inside of a cell.");
// Get the position of the decimal tabstop for this cell.
var decTabStopPos = rtf.LastIndexOf(decimalTabStopStr, valuePos, StringComparison.Ordinal);
@@ -532,7 +532,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
searchPos = valuePos + valueUnifyTabStopsStr.Length;
}
valuePos = rtf.IndexOf(valueUnifyTabStopsStr, searchPos, StringComparison.Ordinal);
- valuePos.Should().BeLessThan(0, $"\"{valueUnifyTabStopsStr}\" should occur only 5 times in a cell in the RTF file after the description text.");
+ valuePos.Should().BeLessThan(0, $"'{valueUnifyTabStopsStr}' should occur only 5 times in a cell in the RTF file after the description text.");
// Search for valueStr - the occurrences for that no additional tabstop should be added.
searchPos = startSearchPos;
@@ -540,13 +540,13 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling)
{
// Get a position of valueStr.
valuePos = rtf.IndexOf(valueStr, searchPos, StringComparison.Ordinal);
- valuePos.Should().BeGreaterThan(0, $"\"{valueStr}\" should occur 35 times in the RTF file after the description text.");
+ valuePos.Should().BeGreaterThan(0, $"'{valueStr}' should occur 35 times in the RTF file after the description text.");
// Get the position of the cell or paragraph containing the value.
var cellPos = rtf.LastIndexOf(cellStr, valuePos, StringComparison.Ordinal);
var parPos = rtf.LastIndexOf(paragraphStr, valuePos, StringComparison.Ordinal);
var containerPos = Math.Max(cellPos, parPos);
- containerPos.Should().BeGreaterThan(0, $"\"{valueStr}\" should occur inside of a cell or paragraph.");
+ containerPos.Should().BeGreaterThan(0, $"'{valueStr}' should occur inside of a cell or paragraph.");
// Check all tabstop’s position elements for this cell or paragraph.
var tabStopPositionSearchPos = containerPos;
@@ -587,8 +587,8 @@ public void Create_Rtf_with_Image()
// Create a renderer for the MigraDoc document.
var rtfRenderer = new RtfDocumentRenderer();
- // Save the document...
- var filename = IOUtility.GetTempFileName("HelloWorld", null);
+ // Save the document…
+ var filename = IOUtility.GetTempFullFileName("HelloWorld", null);
#if DEBUG___
MigraDoc.DocumentObjectModel.IO.DdlWriter dw = new MigraDoc.DocumentObjectModel.IO.DdlWriter(filename + "_0.mdddl");
@@ -605,7 +605,7 @@ public void Create_Rtf_with_Image()
dw.Close();
#endif
- //// ...and start a viewer.
+ //// … and start a viewer.
//PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -623,8 +623,8 @@ public void Create_Rtf_with_Embedded_Base64Image()
// Create a renderer for the MigraDoc document.
var rtfRenderer = new RtfDocumentRenderer();
- // Save the document...
- var filename = IOUtility.GetTempFileName("HelloWorldEmbeddedBase64", null);
+ // Save the document…
+ var filename = IOUtility.GetTempFullFileName("HelloWorldEmbeddedBase64", null);
#if DEBUG___
MigraDoc.DocumentObjectModel.IO.DdlWriter dw = new MigraDoc.DocumentObjectModel.IO.DdlWriter(filename + "_0.mdddl");
@@ -641,7 +641,7 @@ public void Create_Rtf_with_Embedded_Base64Image()
dw.Close();
#endif
- //// ...and start a viewer.
+ //// … and start a viewer.
//PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -748,8 +748,8 @@ public void Create_Rtf_with_Base64Image(string assetName)
// Create a renderer for the MigraDoc document.
var rtfRenderer = new RtfDocumentRenderer();
- // Save the document...
- var filename = IOUtility.GetTempFileName("HelloWorldBase64", null);
+ // Save the document…
+ var filename = IOUtility.GetTempFullFileName("HelloWorldBase64", null);
#if DEBUG___
MigraDoc.DocumentObjectModel.IO.DdlWriter dw = new MigraDoc.DocumentObjectModel.IO.DdlWriter(filename + "_0.mdddl");
@@ -766,7 +766,7 @@ public void Create_Rtf_with_Base64Image(string assetName)
dw.Close();
#endif
- //// ...and start a viewer.
+ //// … and start a viewer.
//PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -804,14 +804,14 @@ public void Test_Heading_Border()
row.Height = Unit.FromCentimeter(10);
}
- var rtfFilename = IOUtility.GetTempFileName("Test_Heading_Border", "rtf");
+ var rtfFilename = IOUtility.GetTempFullFileName("Test_Heading_Border", "rtf");
var rtfRenderer = new RtfDocumentRenderer();
rtfRenderer.Render(document, rtfFilename, Environment.CurrentDirectory);
// Analyze rendered RTF.
var rtf = File.ReadAllText(rtfFilename);
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
// Split by row identifier and skip the first part, which is no row.
var splitByRows = rtf.Split("\\trowd").Skip(1).ToArray();
splitByRows.Length.Should().Be(5, "as there are 5 rows");
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs
index 6709fc87..c90e80f6 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs
@@ -1,23 +1,27 @@
// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
-using PdfSharp.Pdf;
-using PdfSharp.Pdf.IO;
-using PdfSharp.Pdf.Security;
-using MigraDoc.Rendering;
-using Xunit;
-using FluentAssertions;
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Logging;
using MigraDoc.DocumentObjectModel;
+using MigraDoc.Rendering;
using PdfSharp;
using PdfSharp.Drawing;
+using PdfSharp.Drawing.Layout;
using PdfSharp.Fonts;
using PdfSharp.Logging;
+using PdfSharp.Pdf;
+using PdfSharp.Pdf.IO;
+using PdfSharp.Pdf.Security;
+using PdfSharp.Pdf.Signatures;
using PdfSharp.Quality;
using PdfSharp.TestHelper;
using PdfSharp.TestHelper.Analysis.ContentStream;
-using static PdfSharp.TestHelper.SecurityTestHelper;
+using Xunit;
+using FluentAssertions;
using static MigraDoc.Tests.Helper.SecurityTestHelper;
+using static PdfSharp.TestHelper.SecurityTestHelper;
namespace MigraDoc.Tests
{
@@ -87,10 +91,18 @@ public void Dispose()
// - Perm{X} = Permissions test
// ReSharper disable once UnusedParameter.Local
- static void OpenPdf(string filename)
+ static void OpenPdf(Stream stream)
{
#if true_ // Should be "true_" by default and in source control. Change to "true" to enable automatic PDF starting for testing purposes.
- Process.Sta/rt(new ProcessStartInfo(filename) { UseShellExecute = true });
+ if (stream is FileStream fileStream)
+ {
+ var filename = fileStream.Name;
+ Process.Start(new ProcessStartInfo(filename) { UseShellExecute = true });
+ }
+ else if (stream is MemoryStream)
+ {
+ throw new InvalidOperationException("Cannot open PDF from MemoryStream.");
+ }
#endif
}
@@ -99,9 +111,9 @@ public void Test_Write_NoPassword()
{
Skip.If(SkippableTests.SkipSlowTestsUnderDotNetFramework());
- var filename = AddPrefixToFilename("w.pdf");
- WriteStandardTestDocument(filename);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("w.pdf");
+ WriteStandardTestDocument(stream);
+ OpenPdf(stream);
}
[SkippableFact]
@@ -114,9 +126,9 @@ public void Test_Read_NoPassword()
var pdfDoc = PdfReader.Open(tempFile);
var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filename = AddPrefixToFilename("r.pdf");
- pdfRenderer.Save(filename);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("r.pdf");
+ pdfRenderer.Save(stream, false);
+ OpenPdf(stream);
}
[SkippableTheory]
@@ -199,13 +211,13 @@ public void Test_Read_UserPassword_Wrong_Fails(TestOptionsEnum optionsEnum)
var options = TestOptions.ByEnum(optionsEnum);
options.SetDefaultPasswords(true);
- var tempFile = GetSecuredStandardTestDocument(options);
+ var tempDoc = GetSecuredStandardTestDocument(options);
Exception? e = null;
try
{
// ReSharper disable once UnusedVariable
- var pdfDoc = PdfReader.Open(tempFile, PasswordWrong);
+ var pdfDoc = PdfReader.Open(tempDoc, PasswordWrong);
}
catch (Exception ex)
{
@@ -232,9 +244,9 @@ public void Test_Read_UserPassword(TestOptionsEnum optionsEnum)
var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filename = AddPrefixToFilename("r U.pdf", options);
- pdfRenderer.Save(filename);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("r U.pdf", options);
+ pdfRenderer.Save(stream, false);
+ OpenPdf(stream);
}
[SkippableTheory]
@@ -299,9 +311,9 @@ public void Test_Read_OwnerPassword_Without_Import(TestOptionsEnum optionsEnum)
var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filename = AddPrefixToFilename("r O-X_Imp.pdf", options);
- pdfRenderer.Save(filename);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("r O-X_Imp.pdf", options);
+ pdfRenderer.Save(stream, false);
+ OpenPdf(stream);
}
[SkippableTheory]
@@ -347,15 +359,15 @@ public void Test_Read_OwnerPassword(TestOptionsEnum optionsEnum)
var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filename = AddPrefixToFilename("r O.pdf", options);
- pdfRenderer.Save(filename);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("r O.pdf", options);
+ pdfRenderer.Save(stream, false);
+ OpenPdf(stream);
}
[SkippableTheory]
[ClassData(typeof(TestData.AllWriteVersions))]
[ClassData(typeof(TestData.AllWriteVersionsSkipped), Skip = SkippedTestOptionsMessage)]
- public void Test_Write_UserOwnerPassword(TestOptionsEnum optionsEnum)
+ public void Test_Write_UserAndOwnerPassword(TestOptionsEnum optionsEnum)
{
Skip.If(SkippableTests.SkipSlowTestsUnderDotNetFramework());
@@ -454,6 +466,7 @@ public void Test_Read_UserAndOwnerPassword_User_Fails(TestOptionsEnum optionsEnu
[ClassData(typeof(TestData.AllWriteVersions))]
[ClassData(typeof(TestData.AllWriteVersionsSkipped), Skip = SkippedTestOptionsMessage)]
[InlineData(TestOptionsEnum.V5R5ReadOnly)]
+ [InlineData(TestOptionsEnum.V5WithoutMetadata)]
public void Test_Read_UserAndOwnerPassword_User_Import(TestOptionsEnum optionsEnum)
{
Skip.If(SkippableTests.SkipSlowTestsUnderDotNetFramework());
@@ -470,9 +483,9 @@ public void Test_Read_UserAndOwnerPassword_User_Import(TestOptionsEnum optionsEn
var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filename = AddPrefixToFilename("r UO-U_Imp.pdf", options);
- pdfRenderer.Save(filename);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("r UO-U_Imp.pdf", options);
+ pdfRenderer.Save(stream, false);
+ OpenPdf(stream);
}
[SkippableTheory]
@@ -491,9 +504,9 @@ public void Test_Read_UserAndOwnerPassword_Owner(TestOptionsEnum optionsEnum)
var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filename = AddPrefixToFilename("r UO-O.pdf", options);
- pdfRenderer.Save(filename);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("r UO-O.pdf", options);
+ pdfRenderer.Save(stream, false);
+ OpenPdf(stream);
}
[SkippableTheory]
@@ -511,18 +524,18 @@ public void Test_Password_Long(TestOptionsEnum optionsEnum)
options.SetPasswords(userPassword, ownerPassword);
// Write encrypted file.
- var filename = AddPrefixToFilename("w UO-long.pdf", options);
- WriteSecuredStandardTestDocument(filename, options);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("w UO-long.pdf", options);
+ WriteSecuredStandardTestDocument(stream, options);
+ OpenPdf(stream);
// Read encrypted file and write it without encryption.
- var pdfDoc = PdfReader.Open(filename, ownerPassword);
+ var pdfDoc = PdfReader.Open(stream, ownerPassword);
var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filenameRead = AddPrefixToFilename("r UO-long.pdf", options);
- pdfRenderer.Save(filenameRead);
- OpenPdf(filenameRead);
+ var streamRead = AddPrefixToFilename("r UO-long.pdf", options);
+ pdfRenderer.Save(streamRead, false);
+ OpenPdf(streamRead);
}
[SkippableTheory]
@@ -546,18 +559,18 @@ public void Test_Password_Unicode(TestOptionsEnum optionsEnum)
options.SetPasswords(userPassword, ownerPassword);
// Write encrypted file.
- var filename = AddPrefixToFilename("w UO-unic.pdf", options);
- WriteSecuredStandardTestDocument(filename, options);
- OpenPdf(filename);
+ var stream = AddPrefixToFilename("w UO-unic.pdf", options);
+ WriteSecuredStandardTestDocument(stream, options);
+ OpenPdf(stream);
// Read encrypted file and write it without encryption.
- var pdfDoc = PdfReader.Open(filename, ownerPassword);
+ var pdfDoc = PdfReader.Open(stream, ownerPassword);
var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filenameRead = AddPrefixToFilename("r UO-unic.pdf", options);
- pdfRenderer.Save(filenameRead);
- OpenPdf(filenameRead);
+ var streamRead = AddPrefixToFilename("r UO-unic.pdf", options);
+ pdfRenderer.Save(streamRead, false);
+ OpenPdf(streamRead);
}
///
@@ -573,7 +586,7 @@ public void Test_EmbeddedFile_WrapperEncrypted(TestOptionsEnum optionsEnum)
options.SetDefaultPasswords(true, true);
// Write file to embed.
- var filenameEmbedded = "temp.pdf";
+ var filenameEmbedded = PdfFileUtility.GetTempPdfFullFileName("temp_a_embedded");
var documentEmbedded = CreateEmptyTestDocument();
var sectionEmbedded = documentEmbedded.AddSection();
@@ -584,7 +597,7 @@ public void Test_EmbeddedFile_WrapperEncrypted(TestOptionsEnum optionsEnum)
pdfRendererEmbedded.Save(filenameEmbedded);
// Write file containing embedded file with only this embedded file stream encrypted.
- var filename = AddPrefixToFilename("_EmbWrap w UO.pdf", options);
+ var stream = AddPrefixToFilename("_EmbWrap w UO.pdf", options);
var referenceNameEmbedded = "referenceEmbedded";
var document = CreateEmptyTestDocument();
document.AddEmbeddedFile(referenceNameEmbedded, filenameEmbedded);
@@ -595,16 +608,16 @@ public void Test_EmbeddedFile_WrapperEncrypted(TestOptionsEnum optionsEnum)
var link = paragraph.AddHyperlinkToEmbeddedDocument(referenceNameEmbedded + '\\');
link.AddText("Link to embedded file");
- var pdfRenderer = RenderSecuredDocument(document, options);
- pdfRenderer.Save(filename);
+ var streamRenderer = RenderSecuredDocument(document, options);
+ streamRenderer.Save(stream, false);
// Read encrypted file and write it without encryption.
- var pdfDoc = PdfReader.Open(filename, PasswordOwnerDefault);
+ var pdfDoc = PdfReader.Open(stream, PasswordOwnerDefault);
var pdfRendererRead = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filenameRead = AddPrefixToFilename("_EmbWrap r UO.pdf", options);
- pdfRendererRead.Save(filenameRead);
- OpenPdf(filenameRead);
+ var streamRead = AddPrefixToFilename("_EmbWrap r UO.pdf", options);
+ pdfRendererRead.Save(streamRead, false);
+ OpenPdf(streamRead);
}
///
@@ -620,7 +633,7 @@ public void Test_EmbeddedFile_EmbEncrypted(TestOptionsEnum optionsEnum)
options.SetDefaultPasswords(true, true);
// Write file to embed.
- var filenameEmbedded = "temp.pdf";
+ var filenameEmbedded = PdfFileUtility.GetTempPdfFullFileName("temp_b_embedded");
var documentEmbedded = CreateEmptyTestDocument();
var sectionEmbedded = documentEmbedded.AddSection();
@@ -631,7 +644,7 @@ public void Test_EmbeddedFile_EmbEncrypted(TestOptionsEnum optionsEnum)
pdfRendererEmbedded.Save(filenameEmbedded);
// Write file containing embedded file with only this embedded file stream encrypted.
- var filename = AddPrefixToFilename("_EmbEmb w UO.pdf", options);
+ var stream = AddPrefixToFilename("_EmbEmb w UO.pdf", options);
var referenceNameEmbedded = "referenceEmbedded";
var document = CreateEmptyTestDocument();
document.AddEmbeddedFile(referenceNameEmbedded, filenameEmbedded);
@@ -643,15 +656,15 @@ public void Test_EmbeddedFile_EmbEncrypted(TestOptionsEnum optionsEnum)
link.AddText("Link to embedded file");
var pdfRenderer = RenderDocument(document);
- pdfRenderer.Save(filename);
+ pdfRenderer.Save(stream, false);
// Read encrypted file and write it without encryption.
- var pdfDoc = PdfReader.Open(filename, PasswordOwnerDefault);
+ var pdfDoc = PdfReader.Open(stream, PasswordOwnerDefault);
var pdfRendererRead = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filenameRead = AddPrefixToFilename("_EmbEmb r UO.pdf", options);
- pdfRendererRead.Save(filenameRead);
- OpenPdf(filenameRead);
+ var streamRead = AddPrefixToFilename("_EmbEmb r UO.pdf", options);
+ pdfRendererRead.Save(streamRead, false);
+ OpenPdf(streamRead);
}
///
@@ -667,7 +680,7 @@ public void Test_EmbeddedFile_BothEncrypted(TestOptionsEnum optionsEnum)
options.SetDefaultPasswords(true, true);
// Write file to embed.
- var filenameEmbedded = "temp.pdf";
+ var filenameEmbedded = PdfFileUtility.GetTempPdfFullFileName("temp_c_embedded");
var documentEmbedded = CreateEmptyTestDocument();
var sectionEmbedded = documentEmbedded.AddSection();
@@ -678,7 +691,7 @@ public void Test_EmbeddedFile_BothEncrypted(TestOptionsEnum optionsEnum)
pdfRendererEmbedded.Save(filenameEmbedded);
// Write file containing embedded file with only this embedded file stream encrypted.
- var filename = AddPrefixToFilename("_EmbBoth w UO.pdf", options);
+ var stream = AddPrefixToFilename("_EmbBoth w UO.pdf", options);
var referenceNameEmbedded = "referenceEmbedded";
var document = CreateEmptyTestDocument();
document.AddEmbeddedFile(referenceNameEmbedded, filenameEmbedded);
@@ -690,18 +703,21 @@ public void Test_EmbeddedFile_BothEncrypted(TestOptionsEnum optionsEnum)
link.AddText("Link to embedded file");
var pdfRenderer = RenderSecuredDocument(document, options);
- pdfRenderer.Save(filename);
+ pdfRenderer.Save(stream, false);
// Read encrypted file and write it without encryption.
- var pdfDoc = PdfReader.Open(filename, PasswordOwnerDefault);
+ var pdfDoc = PdfReader.Open(stream, PasswordOwnerDefault);
var pdfRendererRead = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filenameRead = AddPrefixToFilename("_EmbBoth r UO.pdf", options);
- pdfRendererRead.Save(filenameRead);
- OpenPdf(filenameRead);
+ var streamRead = AddPrefixToFilename("_EmbBoth r UO.pdf", options);
+ pdfRendererRead.Save(streamRead, false);
+ OpenPdf(streamRead);
}
#if true
- const string SkippedTestEmbeddedFilesMessage = "The feature to encrypt embedded file streams only is not yet correctly implemented. The resulting files may not be readyble with PDF readers.";
+ const string SkippedTestEmbeddedFilesMessage =
+ "The feature to encrypt embedded file streams only is not yet correctly implemented. " +
+ "The resulting files may not be readable with PDF readers. " +
+ "This feature is not very important, as you can instead encrypt the file before importing it.";
[SkippableTheory]
[ClassData(typeof(TestData.V4), Skip = SkippedTestEmbeddedFilesMessage)]
@@ -724,7 +740,7 @@ public void Test_EmbeddedFile_OnlyEmbeddedFileStreamsEncrypted(TestOptionsEnum o
options.SetDefaultPasswords(true, true);
// Write file to embed.
- var filenameEmbedded = "temp.pdf";
+ var filenameEmbedded = PdfFileUtility.GetTempPdfFullFileName("temp_d_embedded");
var documentEmbedded = CreateEmptyTestDocument();
var sectionEmbedded = documentEmbedded.AddSection();
@@ -735,7 +751,7 @@ public void Test_EmbeddedFile_OnlyEmbeddedFileStreamsEncrypted(TestOptionsEnum o
pdfRendererEmbedded.Save(filenameEmbedded);
// Write file containing embedded file with only this embedded file stream encrypted.
- var filename = AddPrefixToFilename("_EmbOnly w UO.pdf", options);
+ var stream = AddPrefixToFilename("_EmbOnly w UO.pdf", options);
var referenceNameEmbedded = "referenceEmbedded";
var document = CreateEmptyTestDocument();
document.AddEmbeddedFile(referenceNameEmbedded, filenameEmbedded);
@@ -749,15 +765,15 @@ public void Test_EmbeddedFile_OnlyEmbeddedFileStreamsEncrypted(TestOptionsEnum o
var pdfRenderer = RenderSecuredDocument(document, options);
var pdfDocument = pdfRenderer.PdfDocument;
pdfDocument.SecurityHandler.EncryptEmbeddedFileStreamsOnly();
- pdfRenderer.Save(filename);
+ pdfRenderer.Save(stream, false);
// Read encrypted file and write it without encryption.
- var pdfDoc = PdfReader.Open(filename, PasswordOwnerDefault);
+ var pdfDoc = PdfReader.Open(stream, PasswordOwnerDefault);
var pdfRendererRead = new PdfDocumentRenderer { PdfDocument = pdfDoc };
- var filenameRead = AddPrefixToFilename("_EmbOnly r UO.pdf", options);
- pdfRendererRead.Save(filenameRead);
- OpenPdf(filenameRead);
+ var streamRead = AddPrefixToFilename("_EmbOnly r UO.pdf", options);
+ pdfRendererRead.Save(streamRead, false);
+ OpenPdf(streamRead);
}
[SkippableTheory]
@@ -777,7 +793,7 @@ public void Test_Permissions(TestOptionsEnum optionsEnum)
// Generates and checks test several test documents with each document containing one more permission property set to false (from none to all properties).
for (var falseCount = 0; falseCount <= count; falseCount++)
{
- var filename = AddPrefixToFilename($"Perm{falseCount}.pdf", options);
+ var stream = AddPrefixToFilename($"Perm{falseCount}.pdf", options);
var pdfRenderer = RenderSecuredStandardTestDocument(options);
var pdfDoc = pdfRenderer.PdfDocument;
@@ -789,9 +805,9 @@ public void Test_Permissions(TestOptionsEnum optionsEnum)
permissionProperty.SetValue(pdfDoc.SecuritySettings, false);
}
- pdfRenderer.Save(filename);
+ pdfRenderer.Save(stream, false);
- var pdfDocRead = PdfReader.Open(filename, PasswordUserDefault);
+ var pdfDocRead = PdfReader.Open(stream, PasswordUserDefault);
// All properties from 0 to falseCount should be false.
for (var i = 0; i < falseCount; i++)
@@ -841,7 +857,7 @@ public void Test_Strings(TestOptionsEnum optionsEnum)
LogHost.Factory = loggerFactory;
// The randomizedTestStringCount should be big enough for a good chance to create encrypted strings that seem to begin with a Unicode BOM.
- // These strings are an important test case to ensure reread as unicode is done after decryption.
+ // These strings are an important test case to ensure reread as Unicode is done after decryption.
const int randomizedTestStringCount = 100000;
// Define test strings. Avoid spaces, hyphens or other characters that may split the strings in two objects in the page content stream.
@@ -866,8 +882,6 @@ public void Test_Strings(TestOptionsEnum optionsEnum)
const int linesPerPage = 50;
- var date = DateTime.Now;
-
var options = TestOptions.ByEnum(optionsEnum);
options.SetDefaultPasswords(true);
@@ -877,7 +891,7 @@ public void Test_Strings(TestOptionsEnum optionsEnum)
var normalStyle = doc.Styles.Normal;
normalStyle.ParagraphFormat.TabStops.AddTabStop(Unit.FromCentimeter(2));
- normalStyle.Font.Name = font.Name2;
+ normalStyle.Font.Name = font.Name;
normalStyle.Font.Size = font.Size;
var section = doc.AddSection();
@@ -901,6 +915,7 @@ public void Test_Strings(TestOptionsEnum optionsEnum)
}
// Render the document.
+ var date = DateTimeOffset.Now;
var pdfRenderer = new PdfDocumentRenderer { Document = doc };
pdfRenderer.RenderDocument();
@@ -917,16 +932,16 @@ public void Test_Strings(TestOptionsEnum optionsEnum)
// Secure and save the document.
SecureDocument(pdfDocument, options);
- var encryptedFile = AddPrefixToFilename("Test_Strings w U.pdf", options);
- pdfRenderer.Save(encryptedFile);
+ var encryptedStream = AddPrefixToFilename("Test_Strings w U.pdf", options);
+ pdfRenderer.Save(encryptedStream, false);
// Open the saved file
- pdfDocument = PdfReader.Open(encryptedFile, PasswordUserDefault);
+ pdfDocument = PdfReader.Open(encryptedStream, PasswordUserDefault);
// Ensure entries in DocumentInformation have not changed.
var documentInfo = pdfDocument.Info;
// We don’t know the saved CreationDate exactly, but it should be less than 5 seconds ago.
- (documentInfo.CreationDate - date).TotalMilliseconds.Should().BeLessThan(5000, "PDF CreationDate should be less than 5 seconds ago.");
+ (documentInfo.CreationDate - date)?.TotalMilliseconds.Should().BeLessThan(5000, "PDF CreationDate should be less than 5 seconds ago.");
documentInfo.Creator.Should().Be(MigraDocProductVersionInformation.Creator, "PDF Creator should match");
//documentInfo.Producer.Should().Be($"{PdfSharpProductVersionInformation.Creator} under {RuntimeInformation.OSDescription}", "PDF Producer should match");
documentInfo.Producer.Should().StartWith(PdfSharpProductVersionInformation.Creator, "PDF Producer should match");
@@ -949,7 +964,7 @@ public void Test_Strings(TestOptionsEnum optionsEnum)
textInfo!.Text.Should().Be(i + ".");
streamEnumerator.Text.MoveAndGetNext(true, out textInfo).Should().BeTrue();
- textInfo!.TextEquals(testString, font, out var encodedText).Should().BeTrue($"encoded test string {i} (\"{encodedText}\") should match in paragraphs");
+ textInfo!.TextEquals(testString, font, out var encodedText).Should().BeTrue($"encoded test string {i} ('{encodedText}') should match in paragraphs");
if (i == 10)
textInfo.Text.Should().Be(@"\(\)\\\(\\\)\\", "test string {i} should match in paragraphs");
@@ -991,5 +1006,134 @@ public void Test_Strings(TestOptionsEnum optionsEnum)
LogHost.Factory = oldLoggerFactory;
}
}
+
+ [Fact]
+ public void Test_Hyperlink()
+ {
+ // Create a MigraDoc document.
+ var document = CreateDocument();
+ // Associate the MigraDoc document with a renderer.
+ var pdfRenderer = new PdfDocumentRenderer
+ {
+ Document = document,
+ PdfDocument = new PdfDocument
+ {
+ PageLayout = PdfPageLayout.SinglePage
+ }
+ };
+ // Layout and render document to PDF.
+ pdfRenderer.RenderDocument();
+ // Set security settings directly on the PDF document
+ var securitySettings = pdfRenderer.PdfDocument.SecuritySettings;
+ securitySettings.OwnerPassword = "Secret";
+ // Save the document...
+ var filename = PdfFileUtility.GetTempPdfFullFileName("HyperlinkWithEncryptionTest");
+ pdfRenderer.PdfDocument.Save(filename);
+ // ...and start a viewer.
+ // Process.Start(new ProcessStartInfo(filename) { UseShellExecute = true });
+ // Creates minimalistic document with hyperlink.
+ static Document CreateDocument()
+ {
+ // Create a new MigraDoc document.
+ var document = new Document();
+ // Add a section to the document.
+ var section = document.AddSection();
+ // Add a paragraph to the section.
+ var paragraph = section.AddParagraph();
+ // Add a hyperlink to a web URL to the paragraph.
+ var hyperlink = paragraph.AddHyperlink("https://docs.pdfsharp.net", HyperlinkType.Url);
+ hyperlink.AddText("link");
+ return document;
+ }
+ }
+
+ [SkippableTheory]
+ [ClassData(typeof(TestData.AllWriteVersions))]
+ [ClassData(typeof(TestData.AllWriteVersionsSkipped), Skip = SkippedTestOptionsMessage)]
+ public void Test_SignedDocument(TestOptionsEnum optionsEnum)
+ {
+ var options = TestOptions.ByEnum(optionsEnum);
+ options.SetDefaultPasswords(true);
+
+ var filename = AddPrefixToFilename("SigningWithEncryptionTest.pdf", options);
+
+ var document = CreateDocument();
+ SecureDocument(document, options);
+
+ // Save the document.
+ document.Save(filename);
+
+ // Read encrypted file and write it without encryption.
+ var pdfDocRead = PdfReader.Open(filename, PasswordUserDefault);
+
+ var filenameRead = AddPrefixToFilename("Read SigningWithEncryptionTest.pdf", options);
+ pdfDocRead.Save(filenameRead);
+
+
+ // Creates minimalistic document with hyperlink.
+ static PdfDocument CreateDocument()
+ {
+ const int requiredAssets = 1014;
+ string? timestampURL = null;
+
+ var certType = "test-cert_rsa_1024";
+ var digestType = PdfMessageDigestType.SHA256;
+
+ IOUtility.EnsureAssetsVersion(requiredAssets);
+
+ var font = new XFont("Verdana", 10, XFontStyleEx.Regular);
+ var fontHeader = new XFont("Verdana", 18, XFontStyleEx.Regular);
+ var document = new PdfDocument();
+ var pdfPage = document.AddPage();
+ var xGraphics = XGraphics.FromPdfPage(pdfPage);
+ var layoutRectangle = new XRect(0, 72, pdfPage.Width.Point, pdfPage.Height.Point);
+ xGraphics.DrawString("Document Signature Test", fontHeader, XBrushes.Black, layoutRectangle, XStringFormats.TopCenter);
+ var textFormatter = new XTextFormatter(xGraphics);
+ layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144);
+
+ var text = "Lorem ipsum...";
+ textFormatter.DrawString(text, font, XBrushes.Black, layoutRectangle, XStringFormats.TopLeft);
+
+ var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216));
+ var options = new DigitalSignatureOptions
+ {
+ // We do not set an appearance handler, so the default handler is used.
+ // It is highly recommended to set an appearance handler to get a nicer representation of the signature.
+ ContactInfo = "John Doe",
+ Location = "Seattle",
+ Reason = "License Agreement",
+ Rectangle = new XRect(pdfPosition.X, pdfPosition.Y, 200, 50),
+ AppName = "PDFsharp Library"
+ };
+
+ Uri? timestampURI = String.IsNullOrEmpty(timestampURL) ? null : new Uri(timestampURL, UriKind.Absolute);
+
+ var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options);
+
+ return document;
+ }
+
+ static X509Certificate2 GetCertificate(string certName)
+ {
+ var certFolder = IOUtility.GetAssetsPath("pdfsharp-6.x/signatures");
+ var pfxFile = Path.Combine(certFolder ?? throw new InvalidOperationException("Call Download-Assets.ps1 before running the tests."), $"{certName}.pfx");
+ var rawData = File.ReadAllBytes(pfxFile);
+
+ // Do not use password literals for real certificates in source code.
+ var certificatePassword = "Seecrit1243"; // Used in certificate.
+
+#if NET9_0_OR_GREATER
+ // New API introduced with .NET 9.
+ var certificate = X509CertificateLoader.LoadPkcs12(rawData, certificatePassword,
+ X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
+#else
+ // Old API, obsolete since .NET 9.
+ var certificate = new X509Certificate2(rawData,
+ certificatePassword,
+ /*X509KeyStorageFlags.MachineKeySet |*/ X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
+#endif
+ return certificate;
+ }
+ }
}
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SpaceBeforeTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SpaceBeforeTests.cs
index ce675634..c6a2b43a 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SpaceBeforeTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SpaceBeforeTests.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using PdfSharp.TestHelper;
@@ -54,10 +54,10 @@ public void Tests_for_SpaceBefore()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("SpaceBefore");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/SpaceBefore");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
#if DEBUG___
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TableTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TableTests.cs
index 87c2164a..f7eb7360 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TableTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TableTests.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using MigraDoc.DocumentObjectModel;
@@ -60,10 +60,10 @@ public void Create_Table_Cell_with_Top_Border()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("TableTopBorder");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/TableTopBorder");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
#if DEBUG___
@@ -117,10 +117,10 @@ public void Create_a_cloned_table()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("ClonedTable");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/ClonedTable");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
@@ -170,10 +170,10 @@ public void Create_a_table_with_cloned_rows()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("ClonedTable");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/ClonedTable");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
}
}
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Template.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Template.cs
index 2854bed5..1c432096 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Template.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Template.cs
@@ -1,4 +1,4 @@
-// MigraDoc - Creating Documents on the Fly
+// MigraDoc - Creating Documents on the Fly
// See the LICENSE file in the solution root for more information.
using MigraDoc.DocumentObjectModel;
@@ -54,10 +54,10 @@ public void Create_Hello_World_TemplateMigraDocTests()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("HelloWorld");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/HelloWorld");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
#if DEBUG___
diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TextTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TextTests.cs
index 9d8c0066..4526c526 100644
--- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TextTests.cs
+++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TextTests.cs
@@ -7,16 +7,13 @@
using MigraDoc.DocumentObjectModel.Fields;
using MigraDoc.Rendering;
using MigraDoc.RtfRendering;
+using PdfSharp.Diagnostics;
using PdfSharp.Fonts;
using PdfSharp.Logging;
using PdfSharp.Quality;
-using PdfSharp.Snippets.Font;
-#if CORE
-#endif
using PdfSharp.TestHelper;
using Xunit;
using FluentAssertions;
-using PdfSharp.Diagnostics;
namespace MigraDoc.Tests
{
@@ -59,10 +56,10 @@ public void Surrogate_Pairs_Test()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("HelloEmoji");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/HelloEmoji");
pdfRenderer.PdfDocument.Save(filename);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocumentIfDebugging(filename);
#if DEBUG___
@@ -116,7 +113,7 @@ public void Document_with_No_Break_Hyphen()
"No\u2011break\u2011hyphen-Test No\u2011break\u2011hyphen-Test No\u2011break\u2011hyphen-Test No\u2011break\u2011hyphen-Test 12345 ");
paragraph.Format.Font.Bold = true;
- var filename = IOUtility.GetTempFileName("DocumentWithNoBreakHyphen", null);
+ var filename = IOUtility.GetTempFullFileName("DocumentWithNoBreakHyphen", null);
var pdfFilename = filename + ".pdf";
var pdfRenderer = new PdfDocumentRenderer { Document = document };
@@ -234,7 +231,7 @@ public void Document_with_No_Break_Hyphen_before_Tabs()
paragraph.AddText("Heading level 2 with no-break hyphen replaced by hyphen character");
paragraph.Style = StyleNames.Heading2;
- var filename = IOUtility.GetTempFileName("DocumentWithNoBreakHyphenBeforeTabs", null);
+ var filename = IOUtility.GetTempFullFileName("DocumentWithNoBreakHyphenBeforeTabs", null);
var pdfFilename = filename + ".pdf";
var pdfRenderer = new PdfDocumentRenderer { Document = document };
@@ -316,7 +313,7 @@ public void DecimalTabulatorTest()
})
};
- var filenamePattern = IOUtility.GetTempFileName("DecimalTabulator{0}", "{1}");
+ var filenamePattern = IOUtility.GetTempFullFileName("DecimalTabulator{0}", "{1}");
foreach (var cultureInfo in cultureInfos)
{
@@ -337,6 +334,8 @@ public void DecimalTabulatorTest()
foreach (var number in numbersByPage[pageIdx])
{
+ // "#,###.##" is intended here to check if numbers with and without a digit before the decimal separator are aligned correctly at the decimal tabulator.
+ // Only the numbers in numbersByPage are formatted here, so the result won’t be an empty string, as it would be if '0' would be formatted with it.
var numberStr = number.ToString("#,###.##", cultureInfo);
foreach (var unit in units)
@@ -450,10 +449,10 @@ public void FooterLayoutTest()
// Layout and render document to PDF.
pdfRenderer.RenderDocument();
- // Save the document...
- var filename = PdfFileUtility.GetTempPdfFileName("FooterTest");
+ // Save the document…
+ var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/footer/FooterTest");
pdfRenderer.PdfDocument.Save(filename);
- //// ...and start a viewer.
+ //// … and start a viewer.
//Process.Sta/rt(new ProcessStartInfo(filename) { UseShellExecute = true });
}
}
diff --git a/src/foundation/src/PDFsharp/docs/AboutFonts.md b/src/foundation/src/PDFsharp/docs/AboutFonts.md
index 8934b30e..ad998c31 100644
--- a/src/foundation/src/PDFsharp/docs/AboutFonts.md
+++ b/src/foundation/src/PDFsharp/docs/AboutFonts.md
@@ -158,7 +158,7 @@ glyph IDs of the Unicode code points of the original UTF-32 encoded text.
The class **PdPdfTrueTypeFontfFont** is used by PDFsharp to represent TrueType fonts in a PDF
document that use the WinAnsiEncoding string representation.
-It uses lesser space for text in PDF file than the CID (character id) glyph encoded text representation,
+It uses lesser space for text in PDF file than the CID (character ID) glyph encoded text representation,
but the character set is limited to (roughly) ANSI characters (codepage 1252).
### PdfType0Font : PdfFont
diff --git a/src/foundation/src/PDFsharp/docs/Notebook.md b/src/foundation/src/PDFsharp/docs/Notebook.md
index 0d7d0b90..2f421a2c 100644
--- a/src/foundation/src/PDFsharp/docs/Notebook.md
+++ b/src/foundation/src/PDFsharp/docs/Notebook.md
@@ -12,11 +12,11 @@
## New C# features
-PDFsharp uses should be compatible with .NET Framework 4.7x and .NET Standard 2.0.
-Therefore only C# features are used that are handled by the compiler, like primary constructors, newer pattern matching,
+PDFsharp should be compatible with .NET Framework 4.6x and .NET Standard 2.0.
+Therefore, only C# features are used that are handled by the compiler, like primary constructors, newer pattern matching,
switch expressions, etc.
However, there are some features that depend on the runtime.
-PDFsharp assemblies need some additional code to work with frameworks other aht .NET 6 or higher.
+PDFsharp assemblies need some additional code to work with frameworks other than .NET 8 or higher.
PDFsharp currently uses the following:
* **init-only setter**
diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj
index 0bb0db5e..1461e37d 100644
--- a/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj
+++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj
@@ -1,7 +1,7 @@
- net6.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PDFsharp.Features
true
@@ -57,5 +57,4 @@
-
diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj
index 95f17553..2b08916a 100644
--- a/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj
+++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj
@@ -1,7 +1,7 @@
- net6.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PDFsharp.Features
true
@@ -57,5 +57,4 @@
-
diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj
index 3b391394..016b7cc1 100644
--- a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj
+++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PDFsharp.Features
true
diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj
index 9f46155e..7ba425db 100644
--- a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj
+++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PDFsharp.Features
true
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj
index 5a877abf..3db12542 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0;net462
+ $(PDFsharpTargetFrameworks_Exe)
CORE
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/Program.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/Program.cs
index 4893e1c8..30b49810 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/Program.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/Program.cs
@@ -7,6 +7,7 @@
//using PdfSharp.Quality;
//using PdfSharp.Snippets.Font;
+using PdfSharp.Fonts;
using PdfSharp.UniversalAccessibility;
using Features = PdfSharp.Features.PdfSharpFeatures;
@@ -16,6 +17,9 @@ class Program
{
static void Main(string[] args)
{
+#if CORE
+ GlobalFontSettings.UseWindowsFontsUnderWindows = true;
+#endif
Memory m = null;
ReadOnlyMemory rom = null;
Span s = null;
@@ -41,14 +45,14 @@ static void Main(string[] args)
// Drawing/text
//features[PdfSharpFeatures.Names.Drawing_text_SurrogateChars__Surrogates].Run();
- //features[PdfSharpFeatures.Names.Drawing_text_SymbolFonts__Symbols].Run();
+ features[PdfSharpFeatures.Names.Drawing_text_SymbolFonts__Symbols].Run();
- //features[PdfSharpFeatures.Names.Font_encoding_Encodings_AnsiEncoding].Run();
+ features[PdfSharpFeatures.Names.Font_encoding_Encodings_AnsiEncoding].Run();
// Pdf/annotations
// Pdf/pdfa
- features[PdfSharpFeatures.Names.Pdf_pdfa_PdfA].Run();
+ //features[PdfSharpFeatures.Names.Pdf_pdfa_PdfA].Run();
@@ -64,8 +68,8 @@ static void Main(string[] args)
// Drawing.text
//new Features.Drawing.AutoFontEncoding().Ensure_one_PdfFontDescriptor_per_FontFace();
- //new Features.Drawing.AutoFontEncoding().Test2();
- //new Features.Drawing.AutoFontEncoding().MeasureString_Test();
+ new Features.Drawing.AutoFontEncoding().Test2();
+ new Features.Drawing.AutoFontEncoding().MeasureString_Test();
//new Features.Drawing.Encodings().Ansi();
//new Features.Drawing.SurrogateChars().Test1();
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/lines/Lines1.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/lines/Lines1.cs
index aa1210e2..eb30adb8 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/lines/Lines1.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/lines/Lines1.cs
@@ -3,7 +3,6 @@
using PdfSharp.Drawing;
-//#pragma warning disable 1591
namespace PdfSharp.Features.Drawing
{
///
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/paths/Paths.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/paths/Paths.cs
index 749623f4..a6657830 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/paths/Paths.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/paths/Paths.cs
@@ -3,7 +3,8 @@
using PdfSharp.Quality;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Drawing
{
public class Paths : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/shapes/RoundedRectangles.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/shapes/RoundedRectangles.cs
index 65f69f24..bb1693e7 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/shapes/RoundedRectangles.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/shapes/RoundedRectangles.cs
@@ -6,7 +6,8 @@
using PdfSharp.Quality;
using PdfSharp.Snippets.Drawing;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Drawing
{
public class RoundedRectangles : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/AutoFontEncoding.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/AutoFontEncoding.cs
index 77ca0165..3ffaeab2 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/AutoFontEncoding.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/AutoFontEncoding.cs
@@ -11,7 +11,8 @@
using PdfSharp.Snippets.Drawing;
using PdfSharp.Snippets.Font;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Drawing
{
public class AutoFontEncoding : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/NotoSans.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/NotoSans.cs
index 07afd297..1c2d7594 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/NotoSans.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/NotoSans.cs
@@ -13,7 +13,8 @@
using PdfSharp.Quality;
using PdfSharp.Snippets.Font;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Drawing
{
public class NotoSans : Feature
@@ -59,7 +60,7 @@ public void Load_all_Noto_Sans()
gfx.DrawString($"{idx + 1:##}: (this is a symbol font)", fontInfo, XBrushes.Black, 20, y);
else
gfx.DrawString($"{idx + 1:##}: {pangram}", font, XBrushes.Black, 20, y);
- gfx.DrawString($" {font.Name2} {Style(font, glyphTypeface)}", fontInfo, XBrushes.DarkBlue, 20, y + 12);
+ gfx.DrawString($" {font.Name} {Style(font, glyphTypeface)}", fontInfo, XBrushes.DarkBlue, 20, y + 12);
y += 30;
if ((idx + 1) % 30 == 0)
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SurrogateChars.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SurrogateChars.cs
index 52208a01..880d7e7f 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SurrogateChars.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SurrogateChars.cs
@@ -6,7 +6,8 @@
using PdfSharp.Quality;
using PdfSharp.Snippets.Drawing;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Drawing
{
public class SurrogateChars : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SymbolFonts.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SymbolFonts.cs
index bb4b7b2f..7d2582a3 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SymbolFonts.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SymbolFonts.cs
@@ -4,7 +4,8 @@
using PdfSharp.Quality;
using PdfSharp.Snippets.Drawing;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Drawing
{
public class SymbolFontFeature : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontResolving.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontResolving.cs
index f90bd702..7ae91835 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontResolving.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontResolving.cs
@@ -6,7 +6,8 @@
using PdfSharp.Quality;
using PdfSharp.Snippets.Font;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features
{
public class FontResolvers : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontSelection.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontSelection.cs
index c3fa1a1f..feab4c31 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontSelection.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontSelection.cs
@@ -3,7 +3,7 @@
using PdfSharp.Quality;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
namespace PdfSharp.Features
{
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/Glyphs.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/Glyphs.cs
index 8f7802ef..400eaf47 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/Glyphs.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/Glyphs.cs
@@ -4,7 +4,7 @@
using PdfSharp.Quality;
using PdfSharp.Snippets.Font;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
namespace PdfSharp.Features.Font
{
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RenderInstalledFonts.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RenderInstalledFonts.cs
index 2db9fb34..6c47cb65 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RenderInstalledFonts.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RenderInstalledFonts.cs
@@ -8,7 +8,8 @@
using System.Drawing.Text;
#endif
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Font
{
public class RenderInstalledFonts : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RotisWinAnsiTester.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RotisWinAnsiTester.cs
index b5af41a3..d47b9feb 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RotisWinAnsiTester.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RotisWinAnsiTester.cs
@@ -15,7 +15,8 @@
using PdfSharp.Fonts;
using PdfSharp.Quality;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Font
{
public class RotisWinAnsiTester : Feature
@@ -169,7 +170,7 @@ static class FaceNames
///
public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
- // Note: PDFsharp calls ResolveTypeface only once for each unique combination
+ // Note that PDFsharp calls ResolveTypeface only once for each unique combination
// of familyName, isBold, and isItalic.
// In this sample we use 6 fonts from the Segoe font family which come with the
@@ -281,7 +282,7 @@ static class FaceNames
///
public byte[] GetFont(string faceName)
{
- // Note: PDFsharp never calls GetFont twice with the same face name.
+ // Note that PDFsharp never calls GetFont twice with the same face name.
// Return the bytes of a font.
switch (faceName)
@@ -394,7 +395,8 @@ static byte[] LoadFontData(string name)
var count = (int)stream.Length;
var data = new byte[count];
- stream.Read(data, 0, count);
+ var read = stream.Read(data, 0, count);
+ Debug.Assert(read == count);
return data;
}
}
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/encoding/Encodings.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/encoding/Encodings.cs
index 1e6a27d9..eb4f78d1 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/encoding/Encodings.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/encoding/Encodings.cs
@@ -10,7 +10,8 @@
using PdfSharp.Snippets.Drawing;
using PdfSharp.Snippets.Font;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Font
{
public class Encodings : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/Info.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/Info.cs
index a688c7b0..749d7fdf 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/Info.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/Info.cs
@@ -1,14 +1,10 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
-using System.Collections.Generic;
-using System.Text;
-using PdfSharp.Pdf;
-using PdfSharp.Pdf.IO;
using PdfSharp.Quality;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.IO
{
public class Info
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/LargePdfFiles.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/LargePdfFiles.cs
index 3d39901f..901b5697 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/LargePdfFiles.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/LargePdfFiles.cs
@@ -15,7 +15,8 @@
using PdfSharp.Snippets.Font;
using PdfSharp.TestHelper;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.IO
{
public class LargePdfFiles : Feature
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/ObjectStreams.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/ObjectStreams.cs
index b065d69c..11a7459c 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/ObjectStreams.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/ObjectStreams.cs
@@ -7,7 +7,8 @@
using PdfSharp.Pdf.IO;
using PdfSharp.TestHelper;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.IO
{
///
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/annotations/LinkAnnotations.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/annotations/LinkAnnotations.cs
index d44c99ff..51b46454 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/annotations/LinkAnnotations.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/annotations/LinkAnnotations.cs
@@ -4,7 +4,8 @@
using PdfSharp.Pdf.IO;
using PdfSharp.Quality;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Pdf
{
public class LinkAnnotations
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/pdfa/PDF-A.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/pdfa/PDF-A.cs
index 205f5a58..860d68b0 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/pdfa/PDF-A.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/pdfa/PDF-A.cs
@@ -8,7 +8,8 @@
using PdfSharp.Quality;
using PdfSharp.UniversalAccessibility;
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features.Pdf
{
public class PdfA
@@ -68,11 +69,11 @@ public void CreatePdfA()
}
sb.End();
- // Save the document...
+ // Save the document…
var fullName = PdfFileUtility.GetTempPdfFullFileName("Features/Pdf-A/Test1" + Capabilities.Build.BuildTag);
document.Save(fullName);
- // ...and start a viewer.
+ // … and start a viewer.
PdfFileUtility.ShowDocument(fullName);
}
}
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj
index f1e8b630..f42bf5a2 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj
@@ -1,7 +1,7 @@
- net6.0;netstandard2.0
+ $(PDFsharpTargetFrameworks_Library)
CORE
@@ -31,5 +31,4 @@
-
diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharpFeatures.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharpFeatures.cs
index 9aba016b..aba659f8 100644
--- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharpFeatures.cs
+++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharpFeatures.cs
@@ -1,7 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-#pragma warning disable 1591
+#pragma warning disable CS1591 // Internal class
+
namespace PdfSharp.Features
{
public class PdfSharpFeatures
diff --git a/src/foundation/src/PDFsharp/src/Directory.Build.props b/src/foundation/src/PDFsharp/src/Directory.Build.props
index f6a2326b..c698c922 100644
--- a/src/foundation/src/PDFsharp/src/Directory.Build.props
+++ b/src/foundation/src/PDFsharp/src/Directory.Build.props
@@ -3,6 +3,7 @@
PDFsharp
+ $(CopyrightPdfSharp)
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs
index a162bca9..1365ff62 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs
@@ -9,12 +9,8 @@
#define DRAW_BMP_
#endif
-using System;
-using System.Diagnostics;
using System.ComponentModel;
-using System.Drawing;
using System.Drawing.Drawing2D;
-using System.Windows.Forms;
using PdfSharp.Drawing;
#if !GDI
@@ -40,7 +36,7 @@ public class PagePreview : UserControl
///
public delegate void RenderEvent(XGraphics gfx);
- private Container components = null!;
+ Container components = null!;
///
/// Initializes a new instance of the class.
@@ -66,15 +62,11 @@ public PagePreview()
//OnLayout();
_zoom = Zoom.FullPage;
- _printableArea = new RectangleF();
+ //_printableArea = new RectangleF();
//virtPageSize = new Size();
//showNonPrintableArea = false;
//virtualPrintableArea = new Rectangle();
- _printableArea.GetType();
- //showNonPrintableArea.GetType();
- //virtualPrintableArea.GetType();
-
// Prevent bogus compiler warnings
_posOffset = new Point();
_virtualPage = new Rectangle();
@@ -150,22 +142,20 @@ protected override void Dispose(bool disposing)
/// Gets or sets the XGraphicsUnit of the page.
/// The default value is XGraphicsUnit.Point.
///
- public XGraphicsUnit PageGraphicsUnit
- {
- get { return _pageGraphicsUnit; }
- set { _pageGraphicsUnit = value; }
- }
- XGraphicsUnit _pageGraphicsUnit = XGraphicsUnit.Point;
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ public XGraphicsUnit PageGraphicsUnit { get; set; } = XGraphicsUnit.Point;
+#if !NET9_0_OR_GREATER
///
/// This property was renamed. Use new property PageGraphicsUnit.
///
[Obsolete("Property renamed, use PageGraphicsUnit")]
public XGraphicsUnit PageUnit
{
- get { return _pageGraphicsUnit; }
- set { _pageGraphicsUnit = value; }
+ get => PageGraphicsUnit;
+ set => PageGraphicsUnit = value;
}
+#endif
///
/// Gets or sets a predefined zoom factor.
@@ -173,10 +163,10 @@ public XGraphicsUnit PageUnit
[DefaultValue((int)Zoom.FullPage), Description("Determines the zoom of the page."), Category("Preview Properties")]
public Zoom Zoom
{
- get { return _zoom; }
+ get => _zoom;
set
{
- if ((int)value < (int)Zoom.Mininum || (int)value > (int)Zoom.Maximum)
+ if ((int)value < (int)Zoom.Minimum || (int)value > (int)Zoom.Maximum)
{
if (!Enum.IsDefined(typeof(Zoom), value))
throw new InvalidEnumArgumentException("value", (int)value, typeof(Zoom));
@@ -196,14 +186,15 @@ public Zoom Zoom
/// Gets or sets an arbitrary zoom factor. The range is from 10 to 800.
///
//[DefaultValue((int)Zoom.FullPage), Description("Determines the zoom of the page."), Category("Preview Properties")]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int ZoomPercent
{
- get { return _zoomPercent; }
+ get => _zoomPercent;
set
{
- if (value < (int)Zoom.Mininum || value > (int)Zoom.Maximum)
+ if (value < (int)Zoom.Minimum || value > (int)Zoom.Maximum)
throw new ArgumentOutOfRangeException("value", value,
- String.Format("Value must between {0} and {1}.", (int)Zoom.Mininum, (int)Zoom.Maximum));
+ String.Format("Value must between {0} and {1}.", (int)Zoom.Minimum, (int)Zoom.Maximum));
if (value != _zoomPercent)
{
@@ -221,9 +212,10 @@ public int ZoomPercent
/// Gets or sets the color of the page.
///
[Description("The background color of the page."), Category("Preview Properties")]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Color PageColor
{
- get { return _pageColor; }
+ get => _pageColor;
set
{
if (value != _pageColor)
@@ -239,9 +231,10 @@ public Color PageColor
/// Gets or sets the color of the desktop.
///
[Description("The color of the desktop."), Category("Preview Properties")]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Color DesktopColor
{
- get { return _desktopColor; }
+ get => _desktopColor;
set
{
if (value != _desktopColor)
@@ -251,7 +244,7 @@ public Color DesktopColor
}
}
}
- internal Color _desktopColor = SystemColors.ControlDark;
+ Color _desktopColor = SystemColors.ControlDark;
///
/// Gets or sets a value indicating whether the scrollbars are visible.
@@ -259,7 +252,7 @@ public Color DesktopColor
[DefaultValue(true), Description("Determines whether the scrollbars are visible."), Category("Preview Properties")]
public bool ShowScrollbars
{
- get { return _showScrollbars; }
+ get => _showScrollbars;
set
{
if (value != _showScrollbars)
@@ -279,7 +272,7 @@ public bool ShowScrollbars
[DefaultValue(true), Description("Determines whether the page visible."), Category("Preview Properties")]
public bool ShowPage
{
- get { return _showPage; }
+ get => _showPage;
set
{
if (value != _showPage)
@@ -295,6 +288,7 @@ public bool ShowPage
/// Gets or sets the page size in point.
///
[Description("Determines the size (in points) of the page."), Category("Preview Properties")]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public XSize PageSize
{
get { return new XSize((int)_pageSize.Width, (int)_pageSize.Height); }
@@ -310,9 +304,10 @@ public XSize PageSize
/// This is a hack for Visual Studio 2008. The designer uses reflection for setting the PageSize property.
/// This fails, even an implicit operator that converts Size to XSize exits.
///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Size PageSizeF
{
- get { return new Size(Convert.ToInt32(_pageSize.Width), Convert.ToInt32(_pageSize.Height)); }
+ get => new(Convert.ToInt32(_pageSize.Width), Convert.ToInt32(_pageSize.Height));
set
{
_pageSize = value;
@@ -346,7 +341,7 @@ public void SetRenderEvent(RenderEvent renderEvent)
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
- private void InitializeComponent()
+ void InitializeComponent()
{
Name = "PagePreview";
Size = new System.Drawing.Size(228, 252);
@@ -554,7 +549,7 @@ internal void CalculatePreviewDimension(out bool zoomChanged)
}
// Bound to zoom limits
- _zoomPercent = Math.Max(Math.Min(_zoomPercent, (int)Zoom.Maximum), (int)Zoom.Mininum);
+ _zoomPercent = Math.Max(Math.Min(_zoomPercent, (int)Zoom.Maximum), (int)Zoom.Minimum);
if ((int)_zoom > 0)
_zoom = (Zoom)_zoomPercent;
@@ -587,13 +582,12 @@ internal void CalculatePreviewDimension(out bool zoomChanged)
zoomChanged = zoomOld != _zoom || zoomPercentOld != _zoomPercent;
if (zoomChanged)
- OnZoomChanged(new EventArgs());
+ OnZoomChanged(EventArgs.Empty);
}
internal void CalculatePreviewDimension()
{
- bool zoomChanged;
- CalculatePreviewDimension(out zoomChanged);
+ CalculatePreviewDimension(out _);
}
internal bool RenderPage(Graphics gfx)
@@ -1043,9 +1037,9 @@ void SetScrollBarRange()
///
Size _virtualCanvas;
- ///
- /// Printable area in point.
- ///
- readonly RectangleF _printableArea;
+ /////
+ ///// Printable area in point.
+ /////
+ //readonly RectangleF _printableArea;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreviewCanvas.cs b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreviewCanvas.cs
index 8b17ed04..471590d0 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreviewCanvas.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreviewCanvas.cs
@@ -45,7 +45,7 @@ protected override void OnPaintBackground(PaintEventArgs e)
{
if (!_preview._showPage)
{
- e.Graphics.Clear(_preview._desktopColor);
+ e.Graphics.Clear(_preview.DesktopColor);
return;
}
bool zoomChanged;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs
index 2cafee8f..752ab6f2 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs
@@ -12,7 +12,7 @@ public enum Zoom
///
/// The smallest possible zoom factor.
///
- Mininum = 10,
+ Minimum = 10,
///
/// The largest possible zoom factor.
@@ -76,7 +76,7 @@ public enum Zoom
///
/// Sets the zoom factor so that the printable area of the document fits horizontally into the window.
- /// Currently not yet implemented and the same as ZoomBestFit.
+ /// Not yet implemented and the same as ZoomBestFit.
///
TextFit = -2,
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj
index 77294d79..f35d5caa 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj
@@ -2,7 +2,7 @@
library
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PdfSharp
true
@@ -10,10 +10,8 @@
$(AssemblyName)
True
..\..\..\..\..\StrongnameKey.snk
-
-
true
+ true
@@ -29,18 +27,14 @@
-
-
+
+
+
+
-
-
-
-
-
-
@@ -125,74 +119,63 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -205,10 +188,11 @@
+
+
-
@@ -221,14 +205,14 @@
-
-
+
+
@@ -245,16 +229,41 @@
-
-
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
@@ -276,12 +285,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -295,12 +330,23 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -308,17 +354,12 @@
-
-
-
-
-
+
-
+
-
-
+
@@ -329,8 +370,10 @@
+
+
@@ -344,25 +387,34 @@
+
+
+
+
+
+
+
+
-
-
+
+
+
@@ -370,7 +422,7 @@
-
+
@@ -378,6 +430,11 @@
+
+
+
+
+
@@ -389,10 +446,10 @@
+
-
@@ -406,23 +463,35 @@
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj
index 9fc703b1..be90e4ec 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj
@@ -2,16 +2,13 @@
library
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PdfSharp
true
WPF
True
..\..\..\..\..\StrongnameKey.snk
-
-
@@ -27,18 +24,12 @@
-
-
+
+
-
-
-
-
-
-
@@ -123,74 +114,63 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -203,10 +183,11 @@
+
+
-
@@ -219,14 +200,14 @@
-
-
+
+
@@ -243,16 +224,41 @@
-
-
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
@@ -274,12 +280,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -293,12 +325,23 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -306,17 +349,12 @@
-
-
-
-
-
+
-
+
-
-
+
@@ -327,8 +365,10 @@
+
+
@@ -342,25 +382,34 @@
+
+
+
+
+
+
+
+
-
-
+
+
+
@@ -376,6 +425,11 @@
+
+
+
+
+
@@ -387,10 +441,10 @@
+
-
@@ -408,17 +462,24 @@
+
+
-
-
+
+
+
+
+
+
+
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj
index 1be8e542..11dfb9bf 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj
@@ -2,13 +2,14 @@
library
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PdfSharp
true
GDI
True
..\..\..\..\..\StrongnameKey.snk
+ true
@@ -16,7 +17,8 @@
-
+
+
@@ -39,7 +41,7 @@
-
+
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj
index 852faa7b..3374afe7 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj
@@ -2,7 +2,7 @@
library
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PdfSharp
true
@@ -16,7 +16,8 @@
-
+
+
@@ -39,7 +40,7 @@
-
+
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeDataMatrix.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeDataMatrix.cs
index de687c0f..5b45f2af 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeDataMatrix.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeDataMatrix.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using System;
@@ -119,7 +119,7 @@ static string CreateEncoding(DataMatrixEncoding dmEncoding, int length)
}
///
- /// Gets or sets the size of the Matrix Quiet Zone.
+ /// Gets or sets the size of the Matrix¹ Quiet Zone.
///
public int QuietZone
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeOmr.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeOmr.cs
index 0a086838..2c592395 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeOmr.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeOmr.cs
@@ -97,7 +97,7 @@ public double MakerThickness
///// Renders the mark at the given position.
/////
///// The mark position to render.
- //private void RenderMark(int position)
+ //void RenderMark(int position)
//{
// double yPos = TopLeft.Y + UpperDistance + position * ToUnit(markDistance).Centimeter;
// //Center mark
@@ -114,7 +114,7 @@ public double MakerThickness
// get { return markDistance; }
// set { markDistance = value; }
//}
- //private MarkDistance markDistance = MarkDistance.Inch2_6;
+ //MarkDistance markDistance = MarkDistance.Inch2_6;
/////
///// Converts a mark distance to an XUnit object.
@@ -161,7 +161,7 @@ public double MakerThickness
// get { return upperDistance; }
// set { upperDistance = value; }
//}
- //private double upperDistance = XUnit.FromInch(8.0 / 6.0).Centimeter;
+ //double upperDistance = XUnit.FromInch(8.0 / 6.0).Centimeter;
/////
///// The lower distance from the last possible mark to the end of the reading zone.
@@ -172,7 +172,7 @@ public double MakerThickness
// get { return lowerDistance; }
// set { lowerDistance = value; }
//}
- //private double lowerDistance = XUnit.FromInch(2.0 / 6.0).Centimeter;
+ //double lowerDistance = XUnit.FromInch(2.0 / 6.0).Centimeter;
/////
///// Gets or sets the width of the reading zone.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/DataMatrixImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/DataMatrixImage.cs
index 60ef2b81..a42147fb 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/DataMatrixImage.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/DataMatrixImage.cs
@@ -103,7 +103,7 @@ internal char[] DataMatrix()
grid = Iec16022Ecc200(matrixColumns, matrixRows, _encoding, _text.Length, _text, len, maxlen, ecclen);
if (grid == null || matrixColumns == 0)
- throw new ArgumentException(BcgSR.DataMatrixNull); //DaSt: ever happen?
+ throw new ArgumentException(BcgSR.DataMatrixNull);
return grid;
}
@@ -619,7 +619,7 @@ public static void InitGalois(int poly)
alog[v] = p;
log[p] = v;
p <<= 1;
- if ((p & b) != 0) //DaSt: check!
+ if ((p & b) != 0)
p ^= poly;
}
}
@@ -644,7 +644,7 @@ public static void InitReedSolomon(int nsym, int index)
rspoly[i] = 1;
for (k = i - 1; k > 0; k--)
{
- if (rspoly[k] != 0) //DaSt: check!
+ if (rspoly[k] != 0)
rspoly[k] = alog[(log[rspoly[k]] + index) % logmod];
rspoly[k] ^= rspoly[k - 1];
}
@@ -668,12 +668,12 @@ public void EncodeReedSolomon(int length, int[] data, ref int[] result)
m = result[rlen - 1] ^ data[i];
for (k = rlen - 1; k > 0; k--)
{
- if ((m != 0) && (rspoly[k] != 0)) //DaSt: check!
+ if (m != 0 && rspoly[k] != 0)
result[k] = result[k - 1] ^ alog[(log[m] + log[rspoly[k]]) % logmod];
else
result[k] = result[k - 1];
}
- if ((m != 0) && (rspoly[0] != 0)) //DaSt: check!
+ if (m != 0 && rspoly[0] != 0)
result[0] = alog[(log[m] + log[rspoly[0]]) % logmod];
else
result[0] = 0;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/OmrData.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/OmrData.cs
index 2549092a..d189113e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/OmrData.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/OmrData.cs
@@ -9,7 +9,7 @@ namespace PdfSharp.Drawing.BarCodes
///
class OmrData
{
- private OmrData()
+ OmrData()
{ }
public static OmrData ForTesting
@@ -48,7 +48,7 @@ public static OmrData ForTesting
/// Adds a mark description by name.
///
/// The name to for setting or unsetting the mark.
- private void AddMarkDescription(string name)
+ void AddMarkDescription(string name)
{
if (_marksInitialized)
throw new InvalidOperationException(BcgSR.OmrAlreadyInitialized);
@@ -57,7 +57,7 @@ private void AddMarkDescription(string name)
++AddedDescriptions;
}
- private void InitMarks()
+ void InitMarks()
{
if (AddedDescriptions == 0)
throw new InvalidOperationException();
@@ -67,7 +67,7 @@ private void InitMarks()
_marksInitialized = true;
}
- private int FindIndex(string name)
+ int FindIndex(string name)
{
if (!_marksInitialized)
InitMarks();
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Extensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Extensions.cs
index 4bd65a76..6caa5a5a 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Extensions.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Extensions.cs
@@ -1,4 +1,7 @@
-using System;
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj
index 0286935a..5f0b37c5 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj
@@ -2,7 +2,7 @@
library
- net6.0;net8.0;netstandard2.0
+ $(PDFsharpTargetFrameworks_Library)
PdfSharp
CORE
True
@@ -14,11 +14,12 @@
-
+
+
-
+
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj
index 657d0d42..c77e86b0 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj
@@ -2,12 +2,13 @@
library
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PdfSharp.Charting
true
True
..\..\..\..\..\StrongnameKey.snk
+ true
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save
deleted file mode 100644
index 303c62a8..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save
+++ /dev/null
@@ -1,300 +0,0 @@
-
-
-
- library
- net6.0-windows;net8.0-windows;net462
- true
- PdfSharp.Charting
- true
- True
- ..\..\..\..\..\StrongnameKey.snk
-
-
-
- true
-
-
-
-
- Charting.Renderers\AreaChartRenderer.cs
-
-
- Charting.Renderers\AreaPlotAreaRenderer.cs
-
-
- Charting.Renderers\AxisRenderer.cs
-
-
- Charting.Renderers\AxisTitleRenderer.cs
-
-
- Charting.Renderers\BarChartRenderer.cs
-
-
- Charting.Renderers\BarClusteredLegendRenderer.cs
-
-
- Charting.Renderers\BarClusteredPlotAreaRenderer.cs
-
-
- Charting.Renderers\BarDataLabelRenderer.cs
-
-
- Charting.Renderers\BarGridlinesRenderer.cs
-
-
- Charting.Renderers\BarPlotAreaRenderer.cs
-
-
- Charting.Renderers\BarStackedPlotAreaRenderer.cs
-
-
- Charting.Renderers\ChartRenderer.cs
-
-
- Charting.Renderers\Colors.cs
-
-
- Charting.Renderers\ColumnChartRenderer.cs
-
-
- Charting.Renderers\ColumnClusteredPlotAreaRenderer.cs
-
-
- Charting.Renderers\ColumnDataLabelRenderer.cs
-
-
- Charting.Renderers\ColumnLikeChartRenderer.cs
-
-
- Charting.Renderers\ColumnLikeGridlinesRenderer.cs
-
-
- Charting.Renderers\ColumnLikeLegendRenderer.cs
-
-
- Charting.Renderers\ColumnLikePlotAreaRenderer.cs
-
-
- Charting.Renderers\ColumnPlotAreaRenderer.cs
-
-
- Charting.Renderers\ColumnStackedPlotAreaRenderer.cs
-
-
- Charting.Renderers\CombinationChartRenderer.cs
-
-
- Charting.Renderers\Converter.cs
-
-
- Charting.Renderers\DataLabelRenderer.cs
-
-
- Charting.Renderers\GridlinesRenderer.cs
-
-
- Charting.Renderers\HorizontalStackedYAxisRenderer.cs
-
-
- Charting.Renderers\HorizontalXAxisRenderer.cs
-
-
- Charting.Renderers\HorizontalYAxisRenderer.cs
-
-
- Charting.Renderers\LegendEntryRenderer.cs
-
-
- Charting.Renderers\LegendRenderer.cs
-
-
- Charting.Renderers\LineChartRenderer.cs
-
-
- Charting.Renderers\LineFormatRenderer.cs
-
-
- Charting.Renderers\LinePlotAreaRenderer.cs
-
-
- Charting.Renderers\MarkerRenderer.cs
-
-
- Charting.Renderers\PieChartRenderer.cs
-
-
- Charting.Renderers\PieClosedPlotAreaRenderer.cs
-
-
- Charting.Renderers\PieDataLabelRenderer.cs
-
-
- Charting.Renderers\PieExplodedPlotAreaRenderer.cs
-
-
- Charting.Renderers\PieLegendRenderer.cs
-
-
- Charting.Renderers\PiePlotAreaRenderer.cs
-
-
- Charting.Renderers\PlotAreaBorderRenderer.cs
-
-
- Charting.Renderers\PlotAreaRenderer.cs
-
-
- Charting.Renderers\Renderer.cs
-
-
- Charting.Renderers\RendererInfo.cs
-
-
- Charting.Renderers\RendererParameters.cs
-
-
- Charting.Renderers\VerticalStackedYAxisRenderer.cs
-
-
- Charting.Renderers\VerticalXAxisRenderer.cs
-
-
- Charting.Renderers\VerticalYAxisRenderer.cs
-
-
- Charting.Renderers\WallRenderer.cs
-
-
- Charting.Renderers\XAxisRenderer.cs
-
-
- Charting.Renderers\YAxisRenderer.cs
-
-
- Charting\Axis.cs
-
-
- Charting\AxisTitle.cs
-
-
- Charting\Chart.cs
-
-
- Charting\ChartFrame.cs
-
-
- Charting\ChartObject.cs
-
-
- Charting\DataLabel.cs
-
-
- Charting\DocumentObject.cs
-
-
- Charting\DocumentObjectCollection.cs
-
-
- Charting\enums\BlankType.cs
-
-
- Charting\enums\ChartType.cs
-
-
- Charting\enums\DataLabelPosition.cs
-
-
- Charting\enums\DataLabelType.cs
-
-
- Charting\enums\DockingType.cs
-
-
- Charting\enums\FontProperties.cs
-
-
- Charting\enums\HorizontalAlignment.cs
-
-
- Charting\enums\LineStyle.cs
-
-
- Charting\enums\MarkerStyle.cs
-
-
- Charting\enums\TickMarkType.cs
-
-
- Charting\enums\Underline.cs
-
-
- Charting\enums\VerticalAlignment.cs
-
-
- Charting\FillFormat.cs
-
-
- Charting\Font.cs
-
-
- Charting\Gridlines.cs
-
-
- Charting\Legend.cs
-
-
- Charting\LineFormat.cs
-
-
- Charting\PlotArea.cs
-
-
- Charting\Point.cs
-
-
- Charting\PSCSR.cs
-
-
- Charting\Series.cs
-
-
- Charting\SeriesCollection.cs
-
-
- Charting\SeriesElements.cs
-
-
- Charting\TickLabels.cs
-
-
- Charting\XSeries.cs
-
-
- Charting\XSeriesElements.cs
-
-
- Charting\XValue.cs
-
-
- Charting\XValues.cs
-
-
- Properties\GlobalDeclarations.cs
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj
index f615f461..54015d53 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj
@@ -2,7 +2,7 @@
library
- net6.0-windows;net8.0-windows;net462
+ $(PDFsharpTargetFrameworks_Windows)
true
PdfSharp.Charting
true
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaChartRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaChartRenderer.cs
index 2d7de575..d2c88f9e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaChartRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaChartRenderer.cs
@@ -144,7 +144,7 @@ internal void InitSeries()
var elements = sri.Series._seriesElements;
if (elements != null)
{
- sri.PointRendererInfos = new PointRendererInfo[elements.Count ];
+ sri.PointRendererInfos = new PointRendererInfo[elements.Count];
for (int pointIdx = 0; pointIdx < sri.PointRendererInfos.Length; ++pointIdx)
{
var pri = new PointRendererInfo();
@@ -155,7 +155,7 @@ internal void InitSeries()
pri.LineFormat = sri.LineFormat;
pri.FillFormat = sri.FillFormat;
if (point._lineFormat != null && !point._lineFormat.Color.IsEmpty)
- pri.LineFormat = new XPen(point._lineFormat.Color, point._lineFormat.Width.Point);
+ pri.LineFormat = new XPen(point._lineFormat.Color, (float_)point._lineFormat.Width.Point);
if (point._fillFormat != null && !point._fillFormat.Color.IsEmpty)
pri.FillFormat = new XSolidBrush(point._fillFormat.Color);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaPlotAreaRenderer.cs
index 9cd85e35..d0857f23 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaPlotAreaRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -39,16 +42,16 @@ internal override void Draw()
{
int count = sri.Series.Elements.Count;
var points = new XPoint[count + 2];
- points[0] = new XPoint(xMajorTick / 2, 0);
+ points[0] = new XPoint((float_)(xMajorTick / 2), 0);
for (int idx = 0; idx < count; idx++)
{
double pointValue = sri.Series.Elements[idx].Value;
if (Double.IsNaN(pointValue))
pointValue = 0;
- points[idx + 1] = new XPoint(idx + xMajorTick / 2, pointValue);
+ points[idx + 1] = new XPoint((float_)(idx + xMajorTick / 2), (float_)pointValue);
}
- points[count + 1] = new XPoint(count - 1 + xMajorTick / 2, 0);
+ points[count + 1] = new XPoint((float_)(count - 1 + xMajorTick / 2), 0);
matrix.TransformPoints(points);
gfx.DrawPolygon(sri.LineFormat, sri.FillFormat, points, XFillMode.Winding);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisRenderer.cs
index d1fa14f5..7e8f9c33 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisRenderer.cs
@@ -62,7 +62,7 @@ protected void InitTickLabels(AxisRendererInfo rendererInfo, XFont defaultFont)
else
{
rendererInfo.TickLabelsFont = defaultFont;
- rendererInfo.TickLabelsBrush = new XSolidBrush(XColors.Black);
+ rendererInfo.TickLabelsBrush = XBrushes.Black;
rendererInfo.TickLabelsFormat = GetDefaultTickLabelsFormat();
}
}
@@ -107,7 +107,7 @@ protected void InitGridlines(AxisRendererInfo rendererInfo)
else if (rendererInfo.Axis?.HasMinorGridlines ?? false)
{
// No minor gridlines object are given, but user asked for.
- rendererInfo.MinorGridlinesLineFormat = new XPen(XColors.Black, DefaultGridLineWidth);
+ rendererInfo.MinorGridlinesLineFormat = new XPen(XColors.Black, (float_)DefaultGridLineWidth);
}
if (rendererInfo.Axis?._majorGridlines != null)
@@ -118,7 +118,7 @@ protected void InitGridlines(AxisRendererInfo rendererInfo)
else if (rendererInfo.Axis?.HasMajorGridlines ?? false)
{
// No major gridlines object are given, but user asked for.
- rendererInfo.MajorGridlinesLineFormat = new XPen(XColors.Black, DefaultGridLineWidth);
+ rendererInfo.MajorGridlinesLineFormat = new XPen(XColors.Black, (float_)DefaultGridLineWidth);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisTitleRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisTitleRenderer.cs
index 4b660513..e50013e1 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisTitleRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisTitleRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -37,7 +40,7 @@ internal override void Format()
points[1].Y = size.Height;
XMatrix matrix = new XMatrix();
- matrix.RotatePrepend(-atri.AxisTitleOrientation);
+ matrix.RotatePrepend((float_)(-atri.AxisTitleOrientation));
matrix.TransformPoints(points);
size.Width = Math.Abs(points[1].X - points[0].X);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredLegendRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredLegendRenderer.cs
index 705d9c29..ba7f8409 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredLegendRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredLegendRenderer.cs
@@ -40,31 +40,31 @@ internal override void Draw()
if (lri.BorderPen != null!)
paddingFactor = 2;
XRect legendRect = lri.Rect;
- legendRect.X += LeftPadding * paddingFactor;
+ legendRect.X += (float_)(LeftPadding * paddingFactor);
if (verticalLegend)
- legendRect.Y = legendRect.Bottom - BottomPadding * paddingFactor;
+ legendRect.Y = (float_)(legendRect.Bottom - BottomPadding * paddingFactor);
else
- legendRect.Y += TopPadding * paddingFactor;
+ legendRect.Y += (float_)(TopPadding * paddingFactor);
if (cri.LegendRendererInfo != null)
{
foreach (var leri in cri.LegendRendererInfo.Entries)
{
if (verticalLegend)
- legendRect.Y -= leri.Height;
+ legendRect.Y -= (float_)leri.Height;
XRect entryRect = legendRect;
- entryRect.Width = leri.Width;
- entryRect.Height = leri.Height;
+ entryRect.Width = (float_)leri.Width;
+ entryRect.Height = (float_)leri.Height;
leri.Rect = entryRect;
parms.RendererInfo = leri;
ler.Draw();
if (verticalLegend)
- legendRect.Y -= EntrySpacing;
+ legendRect.Y -= (float_)EntrySpacing;
else
- legendRect.X += entryRect.Width + EntrySpacing;
+ legendRect.X += (float_)(entryRect.Width + EntrySpacing);
}
}
@@ -72,10 +72,10 @@ internal override void Draw()
if (lri.BorderPen != null)
{
XRect borderRect = lri.Rect;
- borderRect.X += LeftPadding;
- borderRect.Y += TopPadding;
- borderRect.Width -= LeftPadding + RightPadding;
- borderRect.Height -= TopPadding + BottomPadding;
+ borderRect.X += (float_)LeftPadding;
+ borderRect.Y += (float_)TopPadding;
+ borderRect.Width -= (float_)(LeftPadding + RightPadding);
+ borderRect.Height -= (float_)(TopPadding + BottomPadding);
gfx.DrawRectangle(lri.BorderPen, borderRect);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredPlotAreaRenderer.cs
index 34e4026b..708e7e6f 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredPlotAreaRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -72,10 +75,10 @@ protected override void CalcBars()
(y0, y1) = (y1, y0);
}
- points[0].X = y0; // upper left
- points[0].Y = x0;
- points[1].X = y1; // lower right
- points[1].Y = x1;
+ points[0].X =(float_)y0; // upper left
+ points[0].Y =(float_)x0;
+ points[1].X =(float_)y1; // lower right
+ points[1].Y =(float_)x1;
cri.PlotAreaRendererInfo.Matrix.TransformPoints(points);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarGridlinesRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarGridlinesRenderer.cs
index 152a06aa..009bc5f9 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarGridlinesRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarGridlinesRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -56,10 +59,10 @@ internal override void Draw()
lineFormatRenderer = new LineFormatRenderer(gfx, xari.MinorGridlinesLineFormat);
for (double x = xMin + xMinorTick; x < xMax; x += xMinorTick)
{
- points[0].Y = x;
- points[0].X = yMin;
- points[1].Y = x;
- points[1].X = yMax;
+ points[0].Y = (float_)x;
+ points[0].X = (float_)yMin;
+ points[1].Y = (float_)x;
+ points[1].X = (float_)yMax;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -70,10 +73,10 @@ internal override void Draw()
lineFormatRenderer = new LineFormatRenderer(gfx, xari.MajorGridlinesLineFormat);
for (double x = xMin; x <= xMax; x += xMajorTick)
{
- points[0].Y = x;
- points[0].X = yMin;
- points[1].Y = x;
- points[1].X = yMax;
+ points[0].Y = (float_)x;
+ points[0].X = (float_)yMin;
+ points[1].Y = (float_)x;
+ points[1].X = (float_)yMax;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -84,10 +87,10 @@ internal override void Draw()
lineFormatRenderer = new LineFormatRenderer(gfx, yari.MinorGridlinesLineFormat);
for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick)
{
- points[0].Y = xMin;
- points[0].X = y;
- points[1].Y = xMax;
- points[1].X = y;
+ points[0].Y = (float_)xMin;
+ points[0].X = (float_)y;
+ points[1].Y = (float_)xMax;
+ points[1].X = (float_)y;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -98,10 +101,10 @@ internal override void Draw()
lineFormatRenderer = new LineFormatRenderer(gfx, yari.MajorGridlinesLineFormat);
for (double y = yMin; y <= yMax; y += yMajorTick)
{
- points[0].Y = xMin;
- points[0].X = y;
- points[1].Y = xMax;
- points[1].X = y;
+ points[0].Y = (float_)xMin;
+ points[0].X = (float_)y;
+ points[1].Y = (float_)xMax;
+ points[1].X = (float_)y;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarPlotAreaRenderer.cs
index 4cfcc2a9..1fbfe56f 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarPlotAreaRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -33,9 +36,11 @@ internal override void Format()
XRect plotAreaBox = cri.PlotAreaRendererInfo.Rect;
cri.PlotAreaRendererInfo.Matrix = new XMatrix();
- cri.PlotAreaRendererInfo.Matrix.TranslatePrepend(-yMin, xMin);
- cri.PlotAreaRendererInfo.Matrix.Scale(plotAreaBox.Width / (yMax - yMin), plotAreaBox.Height / (xMax - xMin), XMatrixOrder.Append);
- cri.PlotAreaRendererInfo.Matrix.Translate(plotAreaBox.X, plotAreaBox.Y, XMatrixOrder.Append);
+ cri.PlotAreaRendererInfo.Matrix.TranslatePrepend((float_)(-yMin), (float_)xMin);
+ //cri.PlotAreaRendererInfo.Matrix.Scale((float_)(plotAreaBox.Width / (yMax - yMin)), (float_)(plotAreaBox.Height / (xMax - xMin)), XMatrixOrder.Append);
+ cri.PlotAreaRendererInfo.Matrix.ScaleAppend((float_)(plotAreaBox.Width / (yMax - yMin)), (float_)(plotAreaBox.Height / (xMax - xMin)));
+ //cri.PlotAreaRendererInfo.Matrix.Translate(plotAreaBox.X, plotAreaBox.Y, XMatrixOrder.Append);
+ cri.PlotAreaRendererInfo.Matrix.TranslateAppend(plotAreaBox.X, plotAreaBox.Y);
CalcBars();
}
@@ -71,9 +76,9 @@ internal override void Draw()
{
XPoint[] points = new XPoint[2];
points[0].X = 0;
- points[0].Y = xMin;
+ points[0].Y = (float_)xMin;
points[1].X = 0;
- points[1].Y = xMax;
+ points[1].Y = (float_)xMax;
cri.PlotAreaRendererInfo.Matrix.TransformPoints(points);
if (cri.YAxisRendererInfo.MinorGridlinesLineFormat != null)
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarStackedPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarStackedPlotAreaRenderer.cs
index 733925f4..520af014 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarStackedPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarStackedPlotAreaRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -66,10 +69,10 @@ protected override void CalcBars()
yMax += y;
}
- points[0].Y = x0; // top left
- points[0].X = y0;
- points[1].Y = x1; // bottom right
- points[1].X = y1;
+ points[0].Y = (float_)x0; // top left
+ points[0].X = (float_)y0;
+ points[1].Y = (float_)x1; // bottom right
+ points[1].X = (float_)y1;
cri.PlotAreaRendererInfo.Matrix.TransformPoints(points);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ChartRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ChartRenderer.cs
index 52e4c9f0..05e1c5b4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ChartRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ChartRenderer.cs
@@ -32,28 +32,28 @@ protected XRect LayoutLegend()
cri.LegendRendererInfo.X = remainingRect.Left;
cri.LegendRendererInfo.Y = remainingRect.Height / 2 - cri.LegendRendererInfo.Height / 2;
double width = cri.LegendRendererInfo.Width + LegendSpacing;
- remainingRect.X += width;
- remainingRect.Width -= width;
+ remainingRect.X += (float_)width;
+ remainingRect.Width -= (float_)width;
break;
case DockingType.Right:
cri.LegendRendererInfo.X = remainingRect.Right - cri.LegendRendererInfo.Width;
cri.LegendRendererInfo.Y = remainingRect.Height / 2 - cri.LegendRendererInfo.Height / 2;
- remainingRect.Width -= cri.LegendRendererInfo.Width + LegendSpacing;
+ remainingRect.Width -= (float_)(cri.LegendRendererInfo.Width + LegendSpacing);
break;
case DockingType.Top:
cri.LegendRendererInfo.X = remainingRect.Width / 2 - cri.LegendRendererInfo.Width / 2;
cri.LegendRendererInfo.Y = remainingRect.Top;
double height = cri.LegendRendererInfo.Height + LegendSpacing;
- remainingRect.Y += height;
- remainingRect.Height -= height;
+ remainingRect.Y += (float_)height;
+ remainingRect.Height -= (float_)height;
break;
case DockingType.Bottom:
cri.LegendRendererInfo.X = remainingRect.Width / 2 - cri.LegendRendererInfo.Width / 2;
cri.LegendRendererInfo.Y = remainingRect.Bottom - cri.LegendRendererInfo.Height;
- remainingRect.Height -= cri.LegendRendererInfo.Height + LegendSpacing;
+ remainingRect.Height -= (float_)(cri.LegendRendererInfo.Height + LegendSpacing);
break;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Colors.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Colors.cs
index 3acb583e..937b9955 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Colors.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Colors.cs
@@ -14,7 +14,7 @@ sealed class ColumnColors
/// Gets the color for column/bar charts from the specified index.
///
public static XColor Item(int index)
- => XColor.FromArgb((int)SeriesColorValues[index]);
+ => XColor.FromArgb((uint)SeriesColorValues[index]);
///
/// Colors for column/bar charts taken from Excel.
@@ -43,7 +43,7 @@ static class LineColors
/// Gets the color for line charts from the specified index.
///
public static XColor Item(int index)
- => XColor.FromArgb((int)LineColorValues[index]);
+ => XColor.FromArgb((uint)LineColorValues[index]);
///
/// Colors for line charts taken from Excel.
@@ -72,7 +72,7 @@ static class PieColors
/// Gets the color for pie charts from the specified index.
///
public static XColor Item(int index)
- => XColor.FromArgb((int)SectorColorValues[index]);
+ => XColor.FromArgb((uint)SectorColorValues[index]);
///
/// Colors for pie charts taken from Excel.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnClusteredPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnClusteredPlotAreaRenderer.cs
index 22a00301..8b7a6048 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnClusteredPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnClusteredPlotAreaRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -72,10 +75,10 @@ protected override void CalcColumns()
(y0, y1) = (y1, y0);
}
- points[0].X = x0; // upper left
- points[0].Y = y1;
- points[1].X = x1; // lower right
- points[1].Y = y0;
+ points[0].X = (float_)x0; // upper left
+ points[0].Y = (float_)y1;
+ points[1].X = (float_)x1; // lower right
+ points[1].Y = (float_)y0;
cri.PlotAreaRendererInfo.Matrix.TransformPoints(points);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeGridlinesRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeGridlinesRenderer.cs
index bcdcde8f..7422eb3c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeGridlinesRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeGridlinesRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -55,10 +58,10 @@ internal override void Draw()
lineFormatRenderer = new LineFormatRenderer(gfx, xari.MinorGridlinesLineFormat);
for (double x = xMin + xMinorTick; x < xMax; x += xMinorTick)
{
- points[0].X = x;
- points[0].Y = yMin;
- points[1].X = x;
- points[1].Y = yMax;
+ points[0].X = (float_)x;
+ points[0].Y = (float_)yMin;
+ points[1].X = (float_)x;
+ points[1].Y = (float_)yMax;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -69,10 +72,10 @@ internal override void Draw()
lineFormatRenderer = new LineFormatRenderer(gfx, xari.MajorGridlinesLineFormat);
for (double x = xMin; x <= xMax; x += xMajorTick)
{
- points[0].X = x;
- points[0].Y = yMin;
- points[1].X = x;
- points[1].Y = yMax;
+ points[0].X = (float_)x;
+ points[0].Y = (float_)yMin;
+ points[1].X = (float_)x;
+ points[1].Y = (float_)yMax;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -83,10 +86,10 @@ internal override void Draw()
lineFormatRenderer = new LineFormatRenderer(gfx, yari.MinorGridlinesLineFormat);
for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick)
{
- points[0].X = xMin;
- points[0].Y = y;
- points[1].X = xMax;
- points[1].Y = y;
+ points[0].X = (float_)xMin;
+ points[0].Y = (float_)y;
+ points[1].X = (float_)xMax;
+ points[1].Y = (float_)y;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -97,10 +100,10 @@ internal override void Draw()
lineFormatRenderer = new LineFormatRenderer(gfx, yari.MajorGridlinesLineFormat);
for (double y = yMin; y <= yMax; y += yMajorTick)
{
- points[0].X = xMin;
- points[0].Y = y;
- points[1].X = xMax;
- points[1].Y = y;
+ points[0].X = (float_)xMin;
+ points[0].Y = (float_)y;
+ points[1].X = (float_)xMax;
+ points[1].Y = (float_)y;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeLegendRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeLegendRenderer.cs
index 3d85779b..78013535 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeLegendRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeLegendRenderer.cs
@@ -32,10 +32,10 @@ internal override RendererInfo Init()
lri.Legend = cri.Chart._legend;
lri.Font = Converter.ToXFont(lri.Legend._font, cri.DefaultFont);
- lri.FontColor = new XSolidBrush(XColors.Black);
+ lri.FontColor = XBrushes.Black;
if (lri.Legend._lineFormat != null)
- lri.BorderPen = Converter.ToXPen(lri.Legend._lineFormat, XColors.Black, DefaultLineWidth, XDashStyle.Solid);
+ lri.BorderPen = Converter.ToXPen(lri.Legend._lineFormat, XColors.Black, DefaultLineWidth/*, XDashStyle.Solid*/);
lri.Entries = new LegendEntryRendererInfo[cri.SeriesRendererInfos.Length];
int index = 0;
@@ -49,7 +49,7 @@ internal override RendererInfo Init()
};
if (sri.MarkerRendererInfo != null!)
{
- leri.MarkerSize.Width = leri.MarkerSize.Height = sri.MarkerRendererInfo.MarkerSize.Point;
+ leri.MarkerSize.Width = leri.MarkerSize.Height =(float_) sri.MarkerRendererInfo.MarkerSize.Point;
leri.MarkerPen = new XPen(sri.MarkerRendererInfo.MarkerForegroundColor);
leri.MarkerBrush = new XSolidBrush(sri.MarkerRendererInfo.MarkerBackgroundColor);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikePlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikePlotAreaRenderer.cs
index 2f2491be..27faeaf7 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikePlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikePlotAreaRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -32,10 +35,12 @@ internal override void Format()
XRect plotAreaBox = cri.PlotAreaRendererInfo.Rect;
cri.PlotAreaRendererInfo.Matrix = new XMatrix();
- cri.PlotAreaRendererInfo.Matrix.TranslatePrepend(-xMin, yMax);
- cri.PlotAreaRendererInfo.Matrix.Scale(plotAreaBox.Width / xMax, plotAreaBox.Height / (yMax - yMin), XMatrixOrder.Append);
+ cri.PlotAreaRendererInfo.Matrix.TranslatePrepend((float_)(-xMin), (float_)yMax);
+ //cri.PlotAreaRendererInfo.Matrix.Scale(plotAreaBox.Width / xMax, plotAreaBox.Height / (yMax - yMin), XMatrixOrder.Append);
+ cri.PlotAreaRendererInfo.Matrix.ScaleAppend((float_)(plotAreaBox.Width / xMax), (float_)(plotAreaBox.Height / (yMax - yMin)));
cri.PlotAreaRendererInfo.Matrix.ScalePrepend(1, -1);
- cri.PlotAreaRendererInfo.Matrix.Translate(plotAreaBox.X, plotAreaBox.Y, XMatrixOrder.Append);
+ //cri.PlotAreaRendererInfo.Matrix.Translate(plotAreaBox.X, plotAreaBox.Y, XMatrixOrder.Append);
+ cri.PlotAreaRendererInfo.Matrix.TranslateAppend(plotAreaBox.X, plotAreaBox.Y);
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnPlotAreaRenderer.cs
index 27d6d7df..cd080bce 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnPlotAreaRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -55,9 +58,9 @@ internal override void Draw()
if (yMin < 0 && yMax > 0)
{
var points = new XPoint[2];
- points[0].X = xMin;
+ points[0].X = (float_)xMin;
points[0].Y = 0;
- points[1].X = xMax;
+ points[1].X = (float_)xMax;
points[1].Y = 0;
cri.PlotAreaRendererInfo.Matrix.TransformPoints(points);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnStackedPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnStackedPlotAreaRenderer.cs
index 5e9652e1..e3e781d4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnStackedPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnStackedPlotAreaRenderer.cs
@@ -2,7 +2,11 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Diagnostics;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#else
using PdfSharp.Drawing;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -69,10 +73,10 @@ protected override void CalcColumns()
yMax += y;
}
- points[0].X = x0; // upper left
- points[0].Y = y1;
- points[1].X = x1; // lower right
- points[1].Y = y0;
+ points[0].X = (float_)x0; // upper left
+ points[0].Y = (float_)y1;
+ points[1].X = (float_)x1; // lower right
+ points[1].Y = (float_)y0;
cri.PlotAreaRendererInfo.Matrix.TransformPoints(points);
@@ -89,7 +93,7 @@ protected override void CalcColumns()
///
/// Stacked columns are always inside.
///
- protected override bool IsDataInside(double yMin, double yMax, double yValue)
+ protected override bool IsDataInside(double yMin, double yMax, double yValue)
=> true;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Converter.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Converter.cs
index de79ad77..e5faf7c0 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Converter.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Converter.cs
@@ -17,6 +17,9 @@ static class Converter
///
internal static XFont ToXFont(Font? font, XFont defaultFont)
{
+#if PSGFX
+ return null!;
+#else
var xFont = defaultFont;
if (font != null)
{
@@ -37,6 +40,7 @@ internal static XFont ToXFont(Font? font, XFont defaultFont)
xFont = new XFont(fontFamily, size, fontStyle);
}
return xFont;
+#endif
}
///
@@ -46,13 +50,30 @@ internal static XFont ToXFont(Font? font, XFont defaultFont)
internal static XPen ToXPen(LineFormat? lineFormat, XPen defaultPen)
=> ToXPen(lineFormat, defaultPen.Color, defaultPen.Width, defaultPen.DashStyle);
+ internal static XPen ToXPen(LineFormat? lineFormat, XColor defaultColor, double defaultWidth)
+ {
+ return ToXPen(lineFormat, defaultColor, defaultWidth,
+#if PSGFX
+ XDashStyles.Solid);
+#else
+ XDashStyle.Solid);
+#endif
+ }
+
///
/// Creates a XPen based on the specified line format. If not specified color, width and dash style
/// will be taken from the defaultColor, defaultWidth and defaultDashStyle parameters.
///
internal static XPen ToXPen(LineFormat? lineFormat, XColor defaultColor, double defaultWidth,
- XDashStyle defaultDashStyle = XDashStyle.Solid)
+#if PSGFX
+ XDashStyle? defaultDashStyle)
+#else
+ XDashStyle defaultDashStyle )
+#endif
{
+#if PSGFX
+ return XPens.Black;
+#else
XPen pen;
if (lineFormat == null)
{
@@ -75,11 +96,16 @@ internal static XPen ToXPen(LineFormat? lineFormat, XColor defaultColor, double
pen = new XPen(color, width)
{
+#if PSGFX
+ DashStyle = lineFormat.DashStyle,
+#else
DashStyle = lineFormat.DashStyle,
DashOffset = 10 * width
+#endif
};
}
return pen;
+#endif
}
///
@@ -88,9 +114,13 @@ internal static XPen ToXPen(LineFormat? lineFormat, XColor defaultColor, double
///
internal static XBrush ToXBrush(FillFormat? fillFormat, XColor defaultColor)
{
+#if PSGFX
+ return XBrushes.Black;
+#else
if (fillFormat == null || fillFormat.Color.IsEmpty)
return new XSolidBrush(defaultColor);
return new XSolidBrush(fillFormat.Color);
+#endif
}
///
@@ -99,9 +129,13 @@ internal static XBrush ToXBrush(FillFormat? fillFormat, XColor defaultColor)
///
internal static XBrush ToXBrush(Font? font, XColor defaultColor)
{
+#if PSGFX
+ return XBrushes.Black;
+#else
if (font == null || font.Color.IsEmpty)
return new XSolidBrush(defaultColor);
return new XSolidBrush(font.Color);
+#endif
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/DataLabelRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/DataLabelRenderer.cs
index 83db0c3f..d6f3d058 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/DataLabelRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/DataLabelRenderer.cs
@@ -38,7 +38,7 @@ internal override RendererInfo Init()
{
dlri.Format = "0";
dlri.Font = cri.DefaultDataLabelFont;
- dlri.FontColor = new XSolidBrush(XColors.Black);
+ dlri.FontColor = XBrushes.Black;
dlri.Position = DataLabelPosition.InsideEnd;
if (cri.Chart._type == ChartType.Pie2D || cri.Chart._type == ChartType.PieExploded2D)
dlri.Type = DataLabelType.Percent;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalXAxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalXAxisRenderer.cs
index 9a046b46..59c7508b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalXAxisRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalXAxisRenderer.cs
@@ -110,9 +110,9 @@ internal override void Draw()
tickLabelStep = xari.Width / countTickLabels;
//XPoint startPos = new XPoint(xari.X + tickLabelStep / 2, xari.Y + /*xari.TickLabelsHeight +*/ xari.MajorTickMarkWidth);
- XPoint startPos = new XPoint(xari.X + tickLabelStep / 2, xari.Y + xari.TickLabelsHeight);
+ XPoint startPos = new XPoint((float_)(xari.X + tickLabelStep / 2), (float_)(xari.Y + xari.TickLabelsHeight));
if (xari.MajorTickMark != TickMarkType.None)
- startPos.Y += xari.MajorTickMarkWidth;
+ startPos.Y += (float_)xari.MajorTickMarkWidth;
foreach (var xs in (xari.XValues ?? throw new InvalidOperationException()).Cast()) // BUG_OLD???
{
for (int idx = 0; idx < countTickLabels && idx < xs.Count; idx++)
@@ -124,7 +124,7 @@ internal override void Draw()
XSize size = gfx.MeasureString(tickLabel, xari.TickLabelsFont);
gfx.DrawString(tickLabel, xari.TickLabelsFont, xari.TickLabelsBrush, startPos.X - size.Width / 2, startPos.Y);
}
- startPos.X += tickLabelStep;
+ startPos.X += (float_)tickLabelStep;
}
}
@@ -142,13 +142,13 @@ internal override void Draw()
{
int countMinorTickMarks = (int)(xMax / xMinorTick);
double minorTickMarkStep = xari.Width / countMinorTickMarks;
- startPos.X = xari.X;
+ startPos.X = (float_)xari.X;
for (int x = 0; x <= countMinorTickMarks; x++)
{
- points[0].X = startPos.X + minorTickMarkStep * x;
- points[0].Y = minorTickMarkStart;
- points[1].X = points[0].X;
- points[1].Y = minorTickMarkEnd;
+ points[0].X = (float_)(startPos.X + minorTickMarkStep * x);
+ points[0].Y = (float_)minorTickMarkStart;
+ points[1].X = (float_)points[0].X;
+ points[1].Y = (float_)minorTickMarkEnd;
lineFormatRenderer.DrawLine(points[0], points[1]);
}
}
@@ -160,13 +160,13 @@ internal override void Draw()
double majorTickMarkStep = xari.Width;
if (countMajorTickMarks != 0)
majorTickMarkStep = xari.Width / countMajorTickMarks;
- startPos.X = xari.X;
+ startPos.X = (float_)xari.X;
for (int x = 0; x <= countMajorTickMarks; x++)
{
- points[0].X = startPos.X + majorTickMarkStep * x;
- points[0].Y = majorTickMarkStart;
- points[1].X = points[0].X;
- points[1].Y = majorTickMarkEnd;
+ points[0].X = (float_)(startPos.X + majorTickMarkStep * x);
+ points[0].Y = (float_)majorTickMarkStart;
+ points[1].X = (float_)points[0].X;
+ points[1].Y = (float_)majorTickMarkEnd;
lineFormatRenderer.DrawLine(points[0], points[1]);
}
}
@@ -174,10 +174,10 @@ internal override void Draw()
// Axis.
if (xari.LineFormat != null)
{
- points[0].X = xari.X;
- points[0].Y = xari.Y;
- points[1].X = xari.X + xari.Width;
- points[1].Y = xari.Y;
+ points[0].X = (float_)xari.X;
+ points[0].Y = (float_)xari.Y;
+ points[1].X = (float_)(xari.X + xari.Width);
+ points[1].Y = (float_)xari.Y;
if (xari.MajorTickMark != TickMarkType.None)
{
points[0].X -= xari.LineFormat.Width / 2;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalYAxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalYAxisRenderer.cs
index 1db1392f..14da5cdb 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalYAxisRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalYAxisRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -69,7 +72,7 @@ internal override void Format()
}
// Add space for tick marks.
- size.Height += yari.MajorTickMarkWidth * 1.5;
+ size.Height += (float_)(yari.MajorTickMarkWidth * 1.5);
// Measure axis title.
XSize titleSize = new XSize(0, 0);
@@ -80,8 +83,8 @@ internal override void Format()
parms.RendererInfo = yari;
var atr = new AxisTitleRenderer(parms);
atr.Format();
- titleSize.Height = yari.AxisTitleRendererInfo.Height;
- titleSize.Width = yari.AxisTitleRendererInfo.Width;
+ titleSize.Height = (float_)yari.AxisTitleRendererInfo.Height;
+ titleSize.Width = (float_)yari.AxisTitleRendererInfo.Width;
}
yari.Height = size.Height + titleSize.Height;
@@ -107,9 +110,11 @@ internal override void Draw()
double yMinorTick = yari.MinorTick;
XMatrix matrix = new XMatrix();
- matrix.TranslatePrepend(-yMin, -yari.Y);
- matrix.Scale(yari.InnerRect.Width / (yMax - yMin), 1, XMatrixOrder.Append);
- matrix.Translate(yari.X, yari.Y, XMatrixOrder.Append);
+ matrix.TranslatePrepend((float_)(-yMin), (float_)(-yari.Y));
+ //matrix.Scale(yari.InnerRect.Width / (yMax - yMin), 1, XMatrixOrder.Append);
+ matrix.ScaleAppend((float_)(yari.InnerRect.Width / (yMax - yMin)), 1);
+ //matrix.Translate(yari.X, yari.Y, XMatrixOrder.Append);
+ matrix.TranslateAppend((float_)yari.X, (float_)yari.Y);
// Draw axis.
// First draw tick marks, second draw axis.
@@ -124,10 +129,10 @@ internal override void Draw()
{
for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick)
{
- points[0].X = y;
- points[0].Y = minorTickMarkStart;
- points[1].X = y;
- points[1].Y = minorTickMarkEnd;
+ points[0].X = (float_)y;
+ points[0].Y = (float_)minorTickMarkStart;
+ points[1].X = (float_)y;
+ points[1].Y = (float_)minorTickMarkEnd;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -144,18 +149,18 @@ internal override void Draw()
XSize labelSize = gfx.MeasureString(str, yari.TickLabelsFont);
if (yari.MajorTickMark != TickMarkType.None)
{
- labelSize.Height += 1.5f * yari.MajorTickMarkWidth;
- points[0].X = y;
- points[0].Y = majorTickMarkStart;
- points[1].X = y;
- points[1].Y = majorTickMarkEnd;
+ labelSize.Height += 1.5f * (float_)yari.MajorTickMarkWidth;
+ points[0].X = (float_)y;
+ points[0].Y = (float_)majorTickMarkStart;
+ points[1].X = (float_)y;
+ points[1].Y = (float_)majorTickMarkEnd;
matrix.TransformPoints(points);
lineFormatRenderer.DrawLine(points[0], points[1]);
}
XPoint[] layoutText = new XPoint[1];
- layoutText[0].X = y;
- layoutText[0].Y = yari.Y + 1.5 * yari.MajorTickMarkWidth;
+ layoutText[0].X = (float_)y;
+ layoutText[0].Y = (float_)(yari.Y + 1.5 * yari.MajorTickMarkWidth);
matrix.TransformPoints(layoutText);
layoutText[0].X -= labelSize.Width / 2; // Center text vertically.
gfx.DrawString(str, yari.TickLabelsFont, yari.TickLabelsBrush, layoutText[0], xsf);
@@ -163,10 +168,10 @@ internal override void Draw()
if (yari.LineFormat != null)
{
- points[0].X = yMin;
- points[0].Y = yari.Y;
- points[1].X = yMax;
- points[1].Y = yari.Y;
+ points[0].X = (float_)yMin;
+ points[0].Y = (float_)yari.Y;
+ points[1].X = (float_)yMax;
+ points[1].Y = (float_)yari.Y;
matrix.TransformPoints(points);
if (yari.MajorTickMark != TickMarkType.None)
{
@@ -184,7 +189,7 @@ internal override void Draw()
parms.Graphics = gfx;
parms.RendererInfo = yari;
XRect rcTitle = yari.Rect;
- rcTitle.Height = yari.AxisTitleRendererInfo.Height;
+ rcTitle.Height = (float_)yari.AxisTitleRendererInfo.Height;
rcTitle.Y += yari.Rect.Height - rcTitle.Height;
yari.AxisTitleRendererInfo.Rect = rcTitle;
AxisTitleRenderer atr = new AxisTitleRenderer(parms);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendEntryRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendEntryRenderer.cs
index be7cd128..b933016c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendEntryRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendEntryRenderer.cs
@@ -26,8 +26,8 @@ internal override void Format()
var leri = (LegendEntryRendererInfo)_rendererParms.RendererInfo;
// Initialize
- leri.MarkerArea.Width = MaxLegendMarkerWidth;
- leri.MarkerArea.Height = MaxLegendMarkerHeight;
+ leri.MarkerArea.Width = (float_)MaxLegendMarkerWidth;
+ leri.MarkerArea.Height = (float_)MaxLegendMarkerHeight;
leri.MarkerSize = new XSize();
leri.MarkerSize.Width = leri.MarkerArea.Width;
leri.MarkerSize.Height = leri.MarkerArea.Height;
@@ -41,7 +41,7 @@ internal override void Format()
leri.TextSize = gfx.MeasureString(leri.EntryText, leri.LegendRendererInfo.Font);
if (leri.SeriesRendererInfo.Series._chartType == ChartType.Line)
{
- leri.MarkerSize.Width = leri.SeriesRendererInfo.MarkerRendererInfo.MarkerSize.Value;
+ leri.MarkerSize.Width = (float_)leri.SeriesRendererInfo.MarkerRendererInfo.MarkerSize.Value;
leri.MarkerArea.Width = Math.Max(3 * leri.MarkerSize.Width, leri.MarkerArea.Width);
}
@@ -64,20 +64,20 @@ internal override void Draw()
if (leri.SeriesRendererInfo.Series._chartType == ChartType.Line)
{
// Draw line.
- XPoint posLineStart = new XPoint(leri.X, leri.Y + leri.Height / 2);
- XPoint posLineEnd = new XPoint(leri.X + leri.MarkerArea.Width, leri.Y + leri.Height / 2);
+ XPoint posLineStart = new XPoint((float_)leri.X, (float_)(leri.Y + leri.Height / 2));
+ XPoint posLineEnd = new XPoint((float_)(leri.X + leri.MarkerArea.Width), (float_)(leri.Y + leri.Height / 2));
gfx.DrawLine(new XPen(((XSolidBrush)leri.MarkerBrush).Color), posLineStart, posLineEnd);
// Draw marker.
double x = leri.X + leri.MarkerArea.Width / 2;
- XPoint posMarker = new XPoint(x, leri.Y + leri.Height / 2);
+ XPoint posMarker = new XPoint((float_)x, (float_)(leri.Y + leri.Height / 2));
MarkerRenderer.Draw(gfx, posMarker, leri.SeriesRendererInfo.MarkerRendererInfo);
}
else
{
// Draw series rectangle for column, bar or pie charts.
- rect = new XRect(leri.X, leri.Y, leri.MarkerArea.Width, leri.MarkerArea.Height);
- rect.Y += (leri.Height - leri.MarkerArea.Height) / 2;
+ rect = new XRect((float_)leri.X, (float_)leri.Y, leri.MarkerArea.Width, leri.MarkerArea.Height);
+ rect.Y += (float_)((leri.Height - leri.MarkerArea.Height) / 2);
gfx.DrawRectangle(leri.MarkerPen, leri.MarkerBrush, rect);
}
@@ -85,7 +85,7 @@ internal override void Draw()
if (leri.EntryText.Length > 0)
{
rect = leri.Rect;
- rect.X += leri.MarkerArea.Width + LegendEntryRenderer.SpacingBetweenMarkerAndText;
+ rect.X += (float_)(leri.MarkerArea.Width + LegendEntryRenderer.SpacingBetweenMarkerAndText);
XStringFormat format = new XStringFormat();
format.LineAlignment = XLineAlignment.Near;
gfx.DrawString(leri.EntryText, leri.LegendRendererInfo.Font,
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendRenderer.cs
index e6276d9a..f33559dc 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendRenderer.cs
@@ -87,27 +87,27 @@ internal override void Draw()
bool verticalLegend = (lri.Legend._docking == DockingType.Left || lri.Legend._docking == DockingType.Right);
int paddingFactor = 1;
- if (lri.BorderPen != null)
+ if (lri.BorderPen != null!)
paddingFactor = 2;
XRect legendRect = lri.Rect;
- legendRect.X += LegendRenderer.LeftPadding * paddingFactor;
- legendRect.Y += LegendRenderer.TopPadding * paddingFactor;
+ legendRect.X += (float_)(LegendRenderer.LeftPadding * paddingFactor);
+ legendRect.Y += (float_)(LegendRenderer.TopPadding * paddingFactor);
if (cri.LegendRendererInfo != null)
{
foreach (var leri in cri.LegendRendererInfo.Entries)
{
XRect entryRect = legendRect;
- entryRect.Width = leri.Width;
- entryRect.Height = leri.Height;
+ entryRect.Width = (float_)leri.Width;
+ entryRect.Height = (float_)leri.Height;
leri.Rect = entryRect;
parms.RendererInfo = leri;
ler.Draw();
if (verticalLegend)
- legendRect.Y += entryRect.Height + LegendRenderer.EntrySpacing;
+ legendRect.Y += (float_)(entryRect.Height + LegendRenderer.EntrySpacing);
else
- legendRect.X += entryRect.Width + LegendRenderer.EntrySpacing;
+ legendRect.X += (float_)(entryRect.Width + LegendRenderer.EntrySpacing);
}
}
@@ -115,10 +115,10 @@ internal override void Draw()
if (lri.BorderPen != null)
{
XRect borderRect = lri.Rect;
- borderRect.X += LegendRenderer.LeftPadding;
- borderRect.Y += LegendRenderer.TopPadding;
- borderRect.Width -= LegendRenderer.LeftPadding + LegendRenderer.RightPadding;
- borderRect.Height -= LegendRenderer.TopPadding + LegendRenderer.BottomPadding;
+ borderRect.X += (float_)LegendRenderer.LeftPadding;
+ borderRect.Y += (float_)LegendRenderer.TopPadding;
+ borderRect.Width -= (float_)(LegendRenderer.LeftPadding + LegendRenderer.RightPadding);
+ borderRect.Height -= (float_)(LegendRenderer.TopPadding + LegendRenderer.BottomPadding);
gfx.DrawRectangle(lri.BorderPen, borderRect);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineChartRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineChartRenderer.cs
index 60ab3441..56f9be91 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineChartRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineChartRenderer.cs
@@ -1,7 +1,11 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+#if PSGFX
+using PdfSharp.Graphics.Media;
+#else
using PdfSharp.Drawing;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -139,8 +143,11 @@ internal void InitSeries()
sri.LineFormat = Converter.ToXPen(sri.Series._lineFormat, LineColors.Item(seriesIndex), ChartRenderer.DefaultSeriesLineWidth);
else
sri.LineFormat = Converter.ToXPen(sri.Series._lineFormat, sri.Series.MarkerBackgroundColor, ChartRenderer.DefaultSeriesLineWidth);
+#if PSGFX
+ sri.LineFormat.LineJoin = PenLineJoin.Bevel;
+#else
sri.LineFormat.LineJoin = XLineJoin.Bevel;
-
+#endif
MarkerRendererInfo mri = new MarkerRendererInfo();
sri.MarkerRendererInfo = mri;
@@ -154,7 +161,7 @@ internal void InitSeries()
mri.MarkerSize = sri.Series.MarkerSize;
if (mri.MarkerSize.Point == 0)
- mri.MarkerSize = XUnit.FromPoint(7);
+ mri.MarkerSize = XUnit.FromPoint(7); // TODO Why not XUnitPt???
if (!sri.Series._markerStyleInitialized)
//mri.MarkerStyle = (MarkerStyle)(seriesIndex % (Enum.GetNames(typeof(MarkerStyle)).Length - 1) + 1);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineFormatRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineFormatRenderer.cs
index 51578bfa..c6681b32 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineFormatRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineFormatRenderer.cs
@@ -33,7 +33,7 @@ public LineFormatRenderer(XGraphics gfx, LineFormat? lineFormat, double defaultW
//if (visible)
if (visible && lineFormat != null)
{
- _pen = new XPen(lineFormat.Color, width)
+ _pen = new XPen(lineFormat.Color,(float_) width)
{
DashStyle = lineFormat.DashStyle
};
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LinePlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LinePlotAreaRenderer.cs
index e61c0f5e..67a28c14 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LinePlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LinePlotAreaRenderer.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -50,14 +53,14 @@ internal override void Draw()
double v = sri.Series.Elements[idx].Value;
if (Double.IsNaN(v) /*&& cri.Chart.DisplayBlanksAs == BlankType.Zero*/) // TODO_OLD DisplayBlanksAs
v = 0;
- points[idx] = new XPoint(idx + xMajorTick / 2, v);
+ points[idx] = new XPoint((float_)(idx + xMajorTick / 2), (float_)v);
}
matrix.TransformPoints(points);
gfx.DrawLines(sri.LineFormat, points);
DrawMarker(gfx, points, sri);
}
-
+
//gfx.ResetClip();
}
gfx.Restore(state);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/MarkerRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/MarkerRenderer.cs
index 167a0028..95f7f08a 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/MarkerRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/MarkerRenderer.cs
@@ -24,7 +24,7 @@ internal static void Draw(XGraphics graphics, XPoint pos, MarkerRendererInfo ren
double x0, y0, x1, y1;
double g;
- var foreground = new XPen(rendererInfo.MarkerForegroundColor, 0.5);
+ var foreground = new XPen(rendererInfo.MarkerForegroundColor, 0.5f);
var background = new XSolidBrush(rendererInfo.MarkerBackgroundColor);
var gp = new XGraphicsPath();
@@ -117,8 +117,8 @@ internal static void Draw(XGraphics graphics, XPoint pos, MarkerRendererInfo ren
double rad = -(Math.PI / 2); // 90°
for (int idx = 0; idx < 10; idx += 2)
{
- points[idx].X = pos.X + outerCircle * Math.Cos(rad);
- points[idx].Y = pos.Y + outerCircle * Math.Sin(rad);
+ points[idx].X = (float_)(pos.X + outerCircle * Math.Cos(rad));
+ points[idx].Y = (float_)(pos.Y + outerCircle * Math.Sin(rad));
rad += radStep;
}
@@ -126,27 +126,33 @@ internal static void Draw(XGraphics graphics, XPoint pos, MarkerRendererInfo ren
rad = -(Math.PI / 4); // 45°
double x = innerCircle * Math.Cos(rad);
double y = innerCircle * Math.Sin(rad);
- points[1].X = pos.X + x;
- points[1].Y = pos.Y + y;
- points[9].X = pos.X - x;
- points[9].Y = pos.Y + y;
+ points[1].X = (float_)(pos.X + x);
+ points[1].Y = (float_)(pos.Y + y);
+ points[9].X = (float_)(pos.X - x);
+ points[9].Y = (float_)(pos.Y + y);
rad += radStep;
x = innerCircle * Math.Cos(rad);
y = innerCircle * Math.Sin(rad);
- points[3].X = pos.X + x;
- points[3].Y = pos.Y + y;
- points[7].X = pos.X - x;
- points[7].Y = pos.Y + y;
+ points[3].X = (float_)(pos.X + x);
+ points[3].Y = (float_)(pos.Y + y);
+ points[7].X = (float_)(pos.X - x);
+ points[7].Y = (float_)(pos.Y + y);
rad += radStep;
y = innerCircle * Math.Sin(rad);
points[5].X = pos.X;
- points[5].Y = pos.Y + y;
+ points[5].Y = (float_)(pos.Y + y);
+#if PSGFX
+ throw new NotImplementedException("AddLines");
+ //gp.AddLines(points);
+ }
+#else
gp.AddLines(points);
}
break;
+#endif
}
-
gp.CloseFigure();
+
if (rendererInfo.MarkerStyle != MarkerStyle.Dot)
{
graphics.DrawPath(background, gp);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieClosedPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieClosedPlotAreaRenderer.cs
index 717ff666..0cde816a 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieClosedPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieClosedPlotAreaRenderer.cs
@@ -45,10 +45,10 @@ protected override void CalcSectors()
XRect pieRect = cri.PlotAreaRendererInfo.Rect;
if (textMeasure != 0)
{
- pieRect.X += textMeasure;
- pieRect.Y += textMeasure;
- pieRect.Width -= 2 * textMeasure;
- pieRect.Height -= 2 * textMeasure;
+ pieRect.X += (float_)textMeasure;
+ pieRect.Y += (float_)textMeasure;
+ pieRect.Width -= (float_)(2 * textMeasure);
+ pieRect.Height -= (float_)(2 * textMeasure);
}
double startAngle = 270;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieExplodedPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieExplodedPlotAreaRenderer.cs
index 87862276..3363cd90 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieExplodedPlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieExplodedPlotAreaRenderer.cs
@@ -45,10 +45,10 @@ protected override void CalcSectors()
XRect pieRect = cri.PlotAreaRendererInfo.Rect;
if (textMeasure != 0)
{
- pieRect.X += textMeasure;
- pieRect.Y += textMeasure;
- pieRect.Width -= 2 * textMeasure;
- pieRect.Height -= 2 * textMeasure;
+ pieRect.X += (float_)textMeasure;
+ pieRect.Y += (float_)textMeasure;
+ pieRect.Width -= (float_)(2 * textMeasure);
+ pieRect.Height -= (float_)(2 * textMeasure);
}
XPoint origin = new XPoint(pieRect.X + pieRect.Width / 2, pieRect.Y + pieRect.Height / 2);
@@ -74,11 +74,11 @@ protected override void CalcSectors()
sectorStartAngle = Math.Max(0, startAngle + deltaAngle);
sectorSweepAngle = Math.Max(sweepAngle, sweepAngle - deltaAngle);
- p1.X = origin.X + rInnerCircle * Math.Cos(midAngle / 180 * Math.PI);
- p1.Y = origin.Y + rInnerCircle * Math.Sin(midAngle / 180 * Math.PI);
- innerRect.X = p1.X - rOuterCircle + rInnerCircle;
- innerRect.Y = p1.Y - rOuterCircle + rInnerCircle;
- innerRect.Width = (rOuterCircle - rInnerCircle) * 2;
+ p1.X = (float_)(origin.X + rInnerCircle * Math.Cos(midAngle / 180 * Math.PI));
+ p1.Y = (float_)(origin.Y + rInnerCircle * Math.Sin(midAngle / 180 * Math.PI));
+ innerRect.X = (float_)(p1.X - rOuterCircle + rInnerCircle);
+ innerRect.Y = (float_)(p1.Y - rOuterCircle + rInnerCircle);
+ innerRect.Width = (float_)((rOuterCircle - rInnerCircle) * 2);
innerRect.Height = innerRect.Width;
sector.Rect = innerRect;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieLegendRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieLegendRenderer.cs
index f62893fc..982481d9 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieLegendRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieLegendRenderer.cs
@@ -35,10 +35,15 @@ internal PieLegendRenderer(RendererParameters parms) : base(parms)
};
lri.Font = Converter.ToXFont(lri.Legend._font, cri.DefaultFont);
- lri.FontColor = new XSolidBrush(XColors.Black);
+ lri.FontColor = XBrushes.Black;
if (lri.Legend._lineFormat != null)
- lri.BorderPen = Converter.ToXPen(lri.Legend._lineFormat, XColors.Black, DefaultLineWidth, XDashStyle.Solid);
+ lri.BorderPen = Converter.ToXPen(lri.Legend._lineFormat, XColors.Black, DefaultLineWidth,
+#if PSGFX
+ XDashStyles.Solid);
+#else
+ XDashStyle.Solid);
+#endif
XSeries? xseries = null;
if (cri.Chart._xValues != null)
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PiePlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PiePlotAreaRenderer.cs
index 9e4a2de9..0ccafc90 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PiePlotAreaRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PiePlotAreaRenderer.cs
@@ -20,7 +20,7 @@ internal PiePlotAreaRenderer(RendererParameters parms) : base(parms)
///
/// Layouts and calculates the space used by the pie plot area.
///
- internal override void Format()
+ internal override void Format()
=> CalcSectors();
///
@@ -44,14 +44,14 @@ internal override void Draw()
foreach (SectorRendererInfo sector in sri.PointRendererInfos)
{
if (!Double.IsNaN(sector.StartAngle) && !Double.IsNaN(sector.SweepAngle))
- gfx.DrawPie(sector.FillFormat, sector.Rect, sector.StartAngle, sector.SweepAngle);
+ gfx.DrawPie(sector.FillFormat, sector.Rect, (float_)sector.StartAngle, (float_)sector.SweepAngle);
}
// Draw border of the sectors.
foreach (SectorRendererInfo sector in sri.PointRendererInfos)
{
if (!Double.IsNaN(sector.StartAngle) && !Double.IsNaN(sector.SweepAngle))
- gfx.DrawPie(sector.LineFormat, sector.Rect, sector.StartAngle, sector.SweepAngle);
+ gfx.DrawPie(sector.LineFormat, sector.Rect, (float_)sector.StartAngle, (float_)sector.SweepAngle);
}
gfx.Restore(state);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererInfo.cs
index ab22415a..4611b079 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererInfo.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererInfo.cs
@@ -7,11 +7,16 @@ namespace PdfSharp.Charting.Renderers
{
///
/// Represents the base class of all renderer infos.
- /// Renderer infos are used to hold all necessary information and time consuming calculations
+ /// Renderer infos are used to hold all necessary information and time-consuming calculations
/// between rendering cycles.
///
abstract class RendererInfo
- { }
+ {
+ void Foo()
+ {
+ var x = new XFont("xxx", 10);
+ }
+ }
///
/// Base class for all renderer infos which defines an area.
@@ -24,7 +29,7 @@ abstract class AreaRendererInfo : RendererInfo
public virtual double X
{
get => _rect.X;
- set => _rect.X = value;
+ set => _rect.X = (float_)value;
}
///
@@ -33,7 +38,7 @@ public virtual double X
public virtual double Y
{
get => _rect.Y;
- set => _rect.Y = value;
+ set => _rect.Y = (float_)value;
}
///
@@ -42,7 +47,7 @@ public virtual double Y
public virtual double Width
{
get => _rect.Width;
- set => _rect.Width = value;
+ set => _rect.Width = (float_)value;
}
///
@@ -51,7 +56,7 @@ public virtual double Width
public virtual double Height
{
get => _rect.Height;
- set => _rect.Height = value;
+ set => _rect.Height = (float_)value;
}
///
@@ -298,7 +303,7 @@ public override double X
set
{
base.X = value;
- InnerRect.X = value;
+ InnerRect.X = (float_)value;
}
}
@@ -310,7 +315,7 @@ public override double Y
set
{
base.Y = value;
- InnerRect.Y = value + LabelSize.Height / 2;
+ InnerRect.Y = (float_)(value + LabelSize.Height / 2);
}
}
@@ -322,7 +327,7 @@ public override double Height
set
{
base.Height = value;
- InnerRect.Height = value - (InnerRect.Y - Y);
+ InnerRect.Height = (float_)(value - (InnerRect.Y - Y));
}
}
@@ -334,7 +339,7 @@ public override double Width
set
{
base.Width = value;
- InnerRect.Width = value - LabelSize.Width / 2;
+ InnerRect.Width = (float_)(value - LabelSize.Width / 2);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererParameters.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererParameters.cs
index 095e98e2..eda7e7bf 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererParameters.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererParameters.cs
@@ -23,7 +23,7 @@ public RendererParameters()
public RendererParameters(XGraphics gfx, double x, double y, double width, double height)
{
Graphics = gfx;
- Box = new XRect(x, y, width, height);
+ Box = new XRect((float_)x, (float_)y, (float_)width, (float_)height);
}
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalXAxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalXAxisRenderer.cs
index 89fd7c4e..270dfc84 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalXAxisRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalXAxisRenderer.cs
@@ -102,7 +102,7 @@ internal override void Draw()
// Draw tick labels. Each tick label will be aligned centered.
int countTickLabels = (int)xMax;
double tickLabelStep = xari.Height / countTickLabels;
- XPoint startPos = new XPoint(xari.X + xari.Width - xari.MajorTickMarkWidth, xari.Y + tickLabelStep / 2);
+ XPoint startPos = new XPoint((float_)(xari.X + xari.Width - xari.MajorTickMarkWidth), (float_)(xari.Y + tickLabelStep / 2));
if (xari.XValues != null)
{
foreach (XSeries xs in xari.XValues)
@@ -113,7 +113,7 @@ internal override void Draw()
string tickLabel = xv.ValueField;
var size = gfx.MeasureString(tickLabel, xari.TickLabelsFont);
gfx.DrawString(tickLabel, xari.TickLabelsFont, xari.TickLabelsBrush, startPos.X - size.Width, startPos.Y + size.Height / 2);
- startPos.Y += tickLabelStep;
+ startPos.Y += (float_)tickLabelStep;
}
}
}
@@ -132,12 +132,12 @@ internal override void Draw()
{
int countMinorTickMarks = (int)(xMax / xMinorTick);
double minorTickMarkStep = xari.Height / countMinorTickMarks;
- startPos.Y = xari.Y;
+ startPos.Y = (float_)xari.Y;
for (int x = 0; x <= countMinorTickMarks; x++)
{
- points[0].X = minorTickMarkStart;
- points[0].Y = startPos.Y + minorTickMarkStep * x;
- points[1].X = minorTickMarkEnd;
+ points[0].X = (float_)minorTickMarkStart;
+ points[0].Y = (float_)(startPos.Y + minorTickMarkStep * x);
+ points[1].X = (float_)minorTickMarkEnd;
points[1].Y = points[0].Y;
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -148,12 +148,12 @@ internal override void Draw()
{
int countMajorTickMarks = (int)(xMax / xMajorTick);
double majorTickMarkStep = xari.Height / countMajorTickMarks;
- startPos.Y = xari.Y;
+ startPos.Y = (float_)xari.Y;
for (int x = 0; x <= countMajorTickMarks; x++)
{
- points[0].X = majorTickMarkStart;
- points[0].Y = startPos.Y + majorTickMarkStep * x;
- points[1].X = majorTickMarkEnd;
+ points[0].X = (float_)(majorTickMarkStart);
+ points[0].Y = (float_)(startPos.Y + majorTickMarkStep * x);
+ points[1].X = (float_)(majorTickMarkEnd);
points[1].Y = points[0].Y;
lineFormatRenderer.DrawLine(points[0], points[1]);
}
@@ -162,10 +162,10 @@ internal override void Draw()
// Axis.
if (xari.LineFormat != null)
{
- points[0].X = xari.X + xari.Width;
- points[0].Y = xari.Y;
- points[1].X = xari.X + xari.Width;
- points[1].Y = xari.Y + xari.Height;
+ points[0].X = (float_)(xari.X + xari.Width);
+ points[0].Y = (float_)(xari.Y);
+ points[1].X = (float_)(xari.X + xari.Width);
+ points[1].Y = (float_)(xari.Y + xari.Height);
if (xari.MajorTickMark != TickMarkType.None)
{
points[0].Y -= xari.LineFormat.Width / 2;
@@ -178,7 +178,7 @@ internal override void Draw()
var atri = xari.AxisTitleRendererInfo;
if (atri != null && atri.AxisTitleText != null && atri.AxisTitleText.Length > 0)
{
- XRect rect = new XRect(xari.X, xari.Y + xari.Height / 2, atri.AxisTitleSize.Width, 0);
+ var rect = new XRect((float_)xari.X, (float_)(xari.Y + xari.Height / 2), atri.AxisTitleSize.Width, 0);
gfx.DrawString(atri.AxisTitleText, atri.AxisTitleFont, atri.AxisTitleBrush, rect);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalYAxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalYAxisRenderer.cs
index 89b3f0ba..a8450e54 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalYAxisRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalYAxisRenderer.cs
@@ -3,6 +3,9 @@
using PdfSharp.Diagnostics;
using PdfSharp.Drawing;
+#if PSGFX
+using PdfSharp.Graphics.Media.MatrixExtensions;
+#endif
namespace PdfSharp.Charting.Renderers
{
@@ -19,7 +22,7 @@ internal VerticalYAxisRenderer(RendererParameters parms) : base(parms)
{ }
///
- /// Returns a initialized rendererInfo based on the Y axis.
+ /// Returns an initialized rendererInfo based on the Y axis.
///
internal override RendererInfo Init()
{
@@ -70,26 +73,26 @@ internal override void Format()
}
// add space for tickmarks
- size.Width += yari.MajorTickMarkWidth * 1.5;
+ size.Width += (float_)(yari.MajorTickMarkWidth * 1.5);
// Measure axis title
XSize titleSize = new XSize(0, 0);
- if (yari.AxisTitleRendererInfo != null)
+ if (yari.AxisTitleRendererInfo != null!)
{
RendererParameters parms = new RendererParameters();
parms.Graphics = gfx;
parms.RendererInfo = yari;
AxisTitleRenderer atr = new AxisTitleRenderer(parms);
atr.Format();
- titleSize.Height = yari.AxisTitleRendererInfo.Height;
- titleSize.Width = yari.AxisTitleRendererInfo.Width;
+ titleSize.Height = (float_)yari.AxisTitleRendererInfo.Height;
+ titleSize.Width = (float_)yari.AxisTitleRendererInfo.Width;
}
yari.Height = Math.Max(size.Height, titleSize.Height);
yari.Width = size.Width + titleSize.Width;
yari.InnerRect = yari.Rect;
- yari.InnerRect.Y += yari.TickLabelsFont.Height / 2;
+ yari.InnerRect.Y += (float_)(yari.TickLabelsFont.Height / 2);
yari.LabelSize = labelSize;
}
}
@@ -100,7 +103,7 @@ internal override void Format()
internal override void Draw()
{
var yari = ((ChartRendererInfo)_rendererParms.RendererInfo).YAxisRendererInfo;
- if (yari == null)
+ if (yari == null)
return;
double yMin = yari.MinimumScale;
@@ -109,10 +112,12 @@ internal override void Draw()
double yMinorTick = yari.MinorTick;
XMatrix matrix = new XMatrix();
- matrix.TranslatePrepend(-yari.InnerRect.X, yMax);
- matrix.Scale(1, yari.InnerRect.Height / (yMax - yMin), XMatrixOrder.Append);
+ matrix.TranslatePrepend((float_)(-yari.InnerRect.X), (float_)yMax);
+ //matrix.Scale(1, yari.InnerRect.Height / (yMax - yMin), XMatrixOrder.Append);
+ matrix.ScaleAppend(1, (float_)(yari.InnerRect.Height / (yMax - yMin)));
matrix.ScalePrepend(1, -1); // mirror horizontal
- matrix.Translate(yari.InnerRect.X, yari.InnerRect.Y, XMatrixOrder.Append);
+ //matrix.Translate(yari.InnerRect.X, yari.InnerRect.Y, XMatrixOrder.Append);
+ matrix.TranslateAppend(yari.InnerRect.X, yari.InnerRect.Y);
// Draw axis.
// First draw tick marks, second draw axis.
@@ -131,21 +136,25 @@ internal override void Draw()
{
for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick)
{
- points[0].X = minorTickMarkStart;
- points[0].Y = y;
- points[1].X = minorTickMarkEnd;
- points[1].Y = y;
+ points[0].X = (float_)minorTickMarkStart;
+ points[0].Y = (float_)y;
+ points[1].X = (float_)minorTickMarkEnd;
+ points[1].Y = (float_)y;
matrix.TransformPoints(points);
minorTickMarkLineFormat.DrawLine(points[0], points[1]);
}
}
- double lineSpace = yari.TickLabelsFont.GetHeight(); // old: yari.TickLabelsFont.GetHeight(gfx);
+ double lineSpace = (float_)yari.TickLabelsFont.GetHeight(); // old: yari.TickLabelsFont.GetHeight(gfx);
+#if PSGFX
+ int cellSpace = yari.TickLabelsFont.FontFamily.GetLineSpacing((Drawing.XFontStyleEx)yari.TickLabelsFont.Style);
+#else
int cellSpace = yari.TickLabelsFont.FontFamily.GetLineSpacing(yari.TickLabelsFont.Style);
+#endif
double xHeight = yari.TickLabelsFont.Metrics.XHeight;
XSize labelSize = new XSize(0, 0);
- labelSize.Height = lineSpace * xHeight / cellSpace;
+ labelSize.Height = (float_)(lineSpace * xHeight / cellSpace);
int countTickLabels = (int)((yMax - yMin) / yMajorTick) + 1;
for (int idx = 0; idx < countTickLabels; idx++)
@@ -158,21 +167,21 @@ internal override void Draw()
// Draw major tick marks.
if (yari.MajorTickMark != TickMarkType.None)
{
- labelSize.Width += yari.MajorTickMarkWidth * 1.5;
- points[0].X = majorTickMarkStart;
- points[0].Y = y;
- points[1].X = majorTickMarkEnd;
- points[1].Y = y;
+ labelSize.Width += (float_)(yari.MajorTickMarkWidth * 1.5);
+ points[0].X = (float_)majorTickMarkStart;
+ points[0].Y = (float_)y;
+ points[1].X = (float_)majorTickMarkEnd;
+ points[1].Y = (float_)y;
matrix.TransformPoints(points);
majorTickMarkLineFormat.DrawLine(points[0], points[1]);
}
else
- labelSize.Width += SpaceBetweenLabelAndTickmark;
+ labelSize.Width += (float_)SpaceBetweenLabelAndTickmark;
// Draw label text.
var layoutText = new XPoint[1];
layoutText[0].X = yari.InnerRect.X + yari.InnerRect.Width - labelSize.Width;
- layoutText[0].Y = y;
+ layoutText[0].Y = (float_)y;
matrix.TransformPoints(layoutText);
layoutText[0].Y += labelSize.Height / 2; // Center text vertically.
gfx.DrawString(str, yari.TickLabelsFont, yari.TickLabelsBrush, layoutText[0]);
@@ -182,9 +191,9 @@ internal override void Draw()
if (yari.LineFormat != null && yari.LineFormat.Width > 0)
{
points[0].X = yari.InnerRect.X + yari.InnerRect.Width;
- points[0].Y = yMin;
+ points[0].Y = (float_)yMin;
points[1].X = yari.InnerRect.X + yari.InnerRect.Width;
- points[1].Y = yMax;
+ points[1].Y = (float_)yMax;
matrix.TransformPoints(points);
if (yari.MajorTickMark != TickMarkType.None)
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/ChartFrame.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/ChartFrame.cs
index 36e67888..d2d1260c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/ChartFrame.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/ChartFrame.cs
@@ -58,9 +58,13 @@ public void Draw(XGraphics gfx)
Size.Width, Size.Height, 20, 20);
XRect chartRect = new XRect(Location.X, Location.Y, Size.Width, Size.Height);
+#if PSGFX
+ var brush = new XSolidBrush(XColor.FromArgb(0xFFD0DEEF));
+#else
var brush = new XLinearGradientBrush(chartRect, XColor.FromArgb(0xFFD0DEEF), XColors.White,
XLinearGradientMode.Vertical);
- var penBorder = new XPen(XColors.SteelBlue, 2.5);
+#endif
+ var penBorder = new XPen(XColors.SteelBlue, 2.5f);
gfx.DrawRoundedRectangle(penBorder, brush,
Location.X, Location.Y, Size.Width, Size.Height,
15, 15);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/LineFormat.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/LineFormat.cs
index 38144763..7d71b7be 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/LineFormat.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/LineFormat.cs
@@ -49,6 +49,9 @@ internal LineFormat(DocumentObject parent) : base(parent) { }
/// Gets or sets the dash style of the line.
///
public XDashStyle DashStyle { get; set; }
+#if PSGFX
+ = XDashStyles.Solid;
+#endif
///
/// Gets or sets the style of the line.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj
index 0cb4ed7b..b9a40f4e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj
@@ -2,7 +2,7 @@
library
- net6.0;net8.0;netstandard2.0
+ $(PDFsharpTargetFrameworks_Library)
PdfSharp
True
..\..\..\..\..\StrongnameKey.snk
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Properties/GlobalDeclarations.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Properties/GlobalDeclarations.cs
index 36271702..50a15c97 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Properties/GlobalDeclarations.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Properties/GlobalDeclarations.cs
@@ -2,7 +2,12 @@
// See the LICENSE file in the solution root for more information.
global using static System.FormattableString;
-
+#if PSGFX
+global using PdfSharp.Graphics.XGfx;
+#else
+global using float_ = double;
+global using FLOAT_ = double;
+#endif
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs
index 1a05f0d4..01a98b76 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs
@@ -1,7 +1,7 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
using System.Net.Http.Headers;
#endif
using System.Security.Cryptography;
@@ -16,7 +16,7 @@ namespace PdfSharp.Pdf.Signatures
public class PdfSharpDefaultSigner : IDigitalSigner
{
static readonly Oid SignatureTimeStampOid = new("1.2.840.113549.1.9.16.2.14");
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
const string TimestampQueryContentType = "application/timestamp-query";
const string TimestampReplyContentType = "application/timestamp-reply";
#endif
@@ -31,10 +31,10 @@ public PdfSharpDefaultSigner(X509Certificate2 certificate, PdfMessageDigestType
{
Certificate = certificate;
DigestType = digestType;
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
TimeStampAuthorityUri = timeStampAuthorityUri;
#else
- // We dont know how to get a time stamp with .NET Standard.
+ // We don’t know how to get a time stamp with .NET Standard.
// If you need it you must implement your own signer.
if (timeStampAuthorityUri != null)
throw new ArgumentException(nameof(timeStampAuthorityUri) + " must be null when using .NET Framework or .NET Standard.");
@@ -60,7 +60,7 @@ public async Task GetSignatureSizeAsync()
_signatureSize = (await GetSignatureAsync(new MemoryStream([0])).ConfigureAwait(false)).Length;
if (MustAddTimeStamp)
{
- // Add arbitrary padding because TSA timestamp responses length seems to vary from one call to another by 1 byte.
+ // Add arbitrary padding because TSA timestamp response’s length seems to vary from one call to another by 1 byte.
_signatureSize += 10; // 2 was found to be too small. Make it 10 to allow for some DSA variation.
}
else
@@ -100,7 +100,7 @@ public async Task GetSignatureAsync(Stream stream)
PdfMessageDigestType.SHA384 => Oid.FromFriendlyName("sha384", OidGroup.HashAlgorithm),
PdfMessageDigestType.SHA512 => Oid.FromFriendlyName("sha512", OidGroup.HashAlgorithm),
// PdfMessageDigestType.RIPEMD160 => Oid.FromFriendlyName("???"), // ???
- _ => throw new NotImplementedException($"Digest type {DigestType} not supported by this signer.")
+ _ => throw new NotSupportedException($"Digest type {DigestType} not supported by this signer.")
}
} /* { IncludeOption = X509IncludeOption.WholeChain } */;
signer.UnsignedAttributes.Add(new Pkcs9SigningTime());
@@ -109,7 +109,7 @@ public async Task GetSignatureAsync(Stream stream)
if (TimeStampAuthorityUri is not null)
{
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
await AddTimestampFromTSAAsync(signedCms).ConfigureAwait(false);
#else
// Already checked in constructor.
@@ -129,7 +129,7 @@ public async Task GetSignatureAsync(Stream stream)
bool MustAddTimeStamp { get; init; }
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
async Task AddTimestampFromTSAAsync(SignedCms signedCms)
{
// Generate our nonce to identify the pair request-response.
@@ -151,7 +151,7 @@ async Task AddTimestampFromTSAAsync(SignedCms signedCms)
PdfMessageDigestType.SHA384 => HashAlgorithmName.SHA384,
PdfMessageDigestType.SHA512 => HashAlgorithmName.SHA512,
// PdfMessageDigestType.RIPEMD160 => HashAlgorithmName.SHA512, // ???
- _ => throw new NotImplementedException($"Digest type {DigestType} not supported by this signer.")
+ _ => throw new NotSupportedException($"Digest type {DigestType} not supported by this signer.")
};
// Now we generate the request to send to the RFC3161 signing authority.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PsCryptoMsg.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PsCryptoMsg.cs
index b72438d9..11b363cd 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PsCryptoMsg.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PsCryptoMsg.cs
@@ -1,21 +1,44 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
namespace PdfSharp.Pdf.Signatures
{
///
/// PDFsharp cryptography message.
///
- readonly struct PsCryptoMsg(PsCryptoMsgId id, string message)
+ readonly struct PsCryptoMsg(PsCryptoMsgId id, string message) : IErrorMessageInfo
{
+
+ int IErrorMessageInfo.Id => (int)Id;
+
public PsCryptoMsgId Id { get; init; } = id;
+ public string Name { get; init; } = "Crypto" + id;
+
public string Message { get; init; } = message;
public EventId EventId => new((int)Id, EventName);
public string EventName => Id.ToString();
}
+
+ readonly struct PsCryptoMsg2(T id, string message) : IErrorMessageInfo where T : Enum
+ {
+ // I’m frustrated. After 25 year of programming C# I was not able to
+ // cast Id into integer and need to ask ChatGPT to tell me the correct way.
+ int IErrorMessageInfo.Id => (int)(object)Id;
+
+ public T Id { get; init; } = id;
+
+ public string Name { get; init; } = "Crypto" + id;
+
+ public string Message { get; init; } = message;
+
+ public EventId EventId => new((int)(object)Id, EventName);
+
+ public string EventName => Id.ToString();
+ }
}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/enum/PsCryptoMsgId.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/enum/PsCryptoMsgId.cs
index 97bebb8d..dfb171f1 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/enum/PsCryptoMsgId.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/enum/PsCryptoMsgId.cs
@@ -10,7 +10,7 @@ namespace PdfSharp.Pdf.Signatures
///
// GPT 4 recommends to use Crypto instead of Cry as abbreviation,
// because Cry is too ambiguous.
- enum PsCryptoMsgId
+ internal enum PsCryptoMsgId
{
None = 0,
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj
index d0cfee0d..a971d3e4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj
+++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj
@@ -1,7 +1,7 @@
- net6.0;net8.0;netstandard2.0
+ $(PDFsharpTargetFrameworks_Library)
PdfSharp
True
..\..\..\..\..\StrongnameKey.snk
@@ -12,11 +12,12 @@
-
+
+
-
+
@@ -26,7 +27,8 @@
-
+
+
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Configuration.cs b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Configuration.cs
index 9c060891..4c88ae9e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Configuration.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Configuration.cs
@@ -1,6 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+//#define WRITE_LEADING_ZEROS
+
global using System.Diagnostics;
global using PdfSharp.Diagnostics;
global using System.Globalization;
@@ -13,16 +15,50 @@ namespace PdfSharp
///
static class Config
{
+#if WRITE_LEADING_ZEROS
#if DEBUG
public const string SignificantDecimalPlaces0 = "0"; // for testing only
public const string SignificantDecimalPlaces1 = "0.#"; // for testing only
#endif
- public const string SignificantDecimalPlaces2 = "0.##";
- public const string SignificantDecimalPlaces3 = "0.###";
- public const string SignificantDecimalPlaces4 = "0.####";
- public const string SignificantDecimalPlaces7 = "0.#######";
- public const string SignificantDecimalPlaces10 = "0.##########";
- public const string SignificantDecimalPlaces1Plus9 = "0.0#########";
+ public const string SignificantDecimalPlaces2 = "0.##;-0.##;0";
+ public const string SignificantDecimalPlaces3 = "0.###;-0.###;0";
+ public const string SignificantDecimalPlaces4 = "0.####;-0.####;0";
+ public const string SignificantDecimalPlaces7 = "0.#######;-0.#######;0";
+ public const string SignificantDecimalPlaces10 = "0.##########;-0.##########;0"; // Used by DebuggerDisplay only.
+ public const string SignificantDecimalPlaces1Plus9 = "0.0#########;-0.0#########;0.0"; // Used by CReal only.
+
+ // #US263 CReal writes 5 as "5.0" while PdfReal writes 5 as "5".
+
+ public const string NumberFormat2SignificantDecimalPlacesWithLeadingZero = "0.##;-0.##;0";
+ ///
+ /// The number format2 significant decimal places with leading zero
+ ///
+ public const string NumberFormat_0_2 = "0.##;-0.##;0";
+ public const string NumberFormat_x_2 = ".##;-.##;0";
+#else
+#if DEBUG
+ public const string SignificantDecimalPlaces0 = "0"; // for testing only
+ public const string SignificantDecimalPlaces1 = ".#;-.#;0"; // for testing only
+#endif
+ public const string SignificantDecimalPlaces2 = ".##;-.##;0";
+ public const string SignificantDecimalPlaces3 = ".###;-.###;0";
+ public const string SignificantDecimalPlaces4 = ".####;-.####;0";
+ public const string SignificantDecimalPlaces7 = ".#######;-.#######;0";
+ public const string SignificantDecimalPlaces10 = ".##########;-.##########;0"; // Used by DebuggerDisplay only.
+ public const string SignificantDecimalPlaces1Plus9 = ".0#########;-.0#########;0.0"; // Used by CReal only.
+
+ public const string NumberFormat2SignificantDecimalPlacesWithLeadingZero = "0.##;-0.##;0";
+ ///
+ /// The number format2 significant decimal places with leading zero
+ ///
+ public const string NumberFormat_0_2 = "0.##;-0.##;0";
+ public const string NumberFormat_x_2 = ".##;-.##;0";
+#endif
+
+ public const string ColorFormat = NumberFormat_x_2;
+ public const string ColorFormatLz = NumberFormat_0_2;
+ public const string CoordFormat = NumberFormat_x_2;
+ public const string CoordFormatLz = NumberFormat_0_2;
}
static class Const
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs
index da95b90e..5933a2a2 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
//
@@ -8,18 +8,40 @@
#if !DEBUG && TEST_CODE
#warning ***********************************************************
-#warning ***** TEST_CODE MUST BE UNDEFINED FOR FINAL RELEASE *****
+#warning ***** ‘TEST_CODE’ MUST BE UNDEFINED FOR FINAL RELEASE *****
#warning ***********************************************************
#endif
-#if TEST_CODE_ //
-// Ensure not to accidentally rename TEST_CODE to TEST_CODE_.
-// This would compile code previously disabled with #if TEST_CODE_.
-// Rename TEST_CODE always to TEST_CODE_xxx in Directory.Build.targets.
-#warning *****************************************************
-#warning ***** TEST_CODE_ MUST NEVER BE DEFINED *****
-#warning ***** THIS ACCIDENTALLY ACTIVATES EXCLUDED CODE *****
-#warning *****************************************************
+#if !DEBUG && PDFSHARP_DEBUG
+#warning ****************************************************************
+#warning ***** ‘PDFSHARP_DEBUG’ MUST BE UNDEFINED FOR FINAL RELEASE *****
+#warning ****************************************************************
+#endif
+
+#if !DEBUG && FEWER_FRAMEWORKS
+#warning ******************************************************************
+#warning ***** ‘FEWER_FRAMEWORKS’ MUST BE UNDEFINED FOR FINAL RELEASE *****
+#warning ******************************************************************
+#endif
+
+#if TEST_CODE_ // ‘’
+// Ensure not to accidentally rename ‘TEST_CODE’ to ‘TEST_CODE_’.
+// This would compile code previously disabled with ‘#if TEST_CODE_’.
+// Rename ‘TEST_CODE’ always to ‘TEST_CODExxx’ in ‘Directory.Build.targets’.
+#warning *********************************************************
+#warning ***** ‘TEST_CODE_’ MUST NEVER BE DEFINED *****
+#warning ***** THIS MAY ACCIDENTALLY ACTIVATES EXCLUDED CODE *****
+#warning *********************************************************
+#endif
+
+#if PDFSHARP_DEBUG_ // ‘’
+// Ensure not to accidentally rename ‘PDFSHARP_DEBUG’ to ‘PDFSHARP_DEBUG_’.
+// This would compile code previously disabled with ‘#if PDFSHARP_DEBUG_’.
+// Rename ‘PDFSHARP_DEBUG’ always to ‘PDFSHARP_DEBUGxxx’ in ‘Directory.Build.targets’.
+#warning *********************************************************
+#warning ***** ‘PDFSHARP_DEBUG_’ MUST NEVER BE DEFINED *****
+#warning ***** THIS MAY ACCIDENTALLY ACTIVATES EXCLUDED CODE *****
+#warning *********************************************************
#endif
#if GDI && WPF
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/DebugBreakHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/DebugBreakHelper.cs
new file mode 100644
index 00000000..1735e782
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/DebugBreakHelper.cs
@@ -0,0 +1,143 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+#if PDFSHARP_DEBUG
+#if !DEBUG
+#warning "PDFSHARP_DEBUG set in a RELEASE build."
+#endif
+
+using PdfSharp.Pdf;
+
+// Missing XML comment for publicly visible type or member because
+// this is empira internal stuff not contained in a release build.
+#pragma warning disable CS1591 // Internal class
+
+namespace PdfSharp.Diagnostics
+{
+ ///
+ /// Some flags that can be set or test anywhere in the code
+ /// to communicate a particular condition from e.g. test code
+ /// into PDFsharp. Some kind of code-based conditional break-points.
+ /// MUST NOT COME INTO NUGET PACKAGES!
+ ///
+ public static class DebugBreakHelper
+ {
+ ///
+ /// Gets or sets conditional break point 1.
+ ///
+ public static bool ShouldBreak1 { get; set; }
+
+ ///
+ /// Gets or sets conditional break point 2.
+ ///
+ public static bool ShouldBreak2 { get; set; }
+
+ ///
+ /// Gets or sets conditional break point 3.
+ ///
+ public static bool ShouldBreak3 { get; set; }
+
+ ///
+ /// Gets or sets conditional break point 4.
+ ///
+ public static bool ShouldBreak4 { get; set; }
+
+ ///
+ /// Gets or sets conditional break point 5.
+ ///
+ public static bool ShouldBreak5 { get; set; }
+
+ //public static void BreakOn1()
+ //{
+ // if (ShouldBreak1)
+ // Debugger.Break();
+ //}
+ }
+
+ ///
+ /// An empira internal class for debugging and testing PDFsharp.
+ ///
+ public class PdfSharpDebug
+ {
+ public void AddObjectId(PdfObjectID id)
+ => ObjectIdSet.Add(MakeId(id.ObjectNumber, id.GenerationNumber), null);
+
+ public void AddObjectId(int objectNumber, int generationNumber)
+ => ObjectIdSet.Add(MakeId(objectNumber, generationNumber), null);
+
+ public void AddItemNumber(PdfItem item)
+ => ItemNumberSet.Add(item.ItemNumber, null);
+
+ public void AddItemNumber(int itemNumber)
+ => ItemNumberSet.Add(itemNumber, null);
+
+ public void AddDeadContainer(PdfContainer cont)
+ {
+ DeadContainers.Add(cont, null);
+ }
+
+ public bool IsInObjectIdSet(PdfObjectID id) => ObjectIdSet.ContainsKey(MakeId(id));
+
+ public bool IsInObjectIdSet(int objectNumber, int generationNumber = 0) => ObjectIdSet.ContainsKey(MakeId(objectNumber, generationNumber));
+
+ public bool IsInItemNumberSet(PdfItem item) => ItemNumberSet.ContainsKey(item.ItemNumber);
+
+ public bool IsInItemNumberSet(int itemNumber) => ItemNumberSet.ContainsKey(itemNumber);
+
+ public readonly Dictionary Stuff = [];
+
+ // ===
+
+ public bool BreakInCrossReferenceTable { get; set; }
+
+ public bool SaveDocumentWithNoPages { get; set; }
+
+ public bool AllowOpenWithUserPasswordOnly { get; set; }
+
+ public bool SaveImportedDocument { get; set; }
+
+ // ===
+
+ ///
+ /// Gets or sets conditional break point 1.
+ ///
+ public bool ShouldBreak1 { get; set; }
+
+ ///
+ /// Gets or sets conditional break point 2.
+ ///
+ public bool ShouldBreak2 { get; set; }
+
+ ///
+ /// Gets or sets conditional break point 3.
+ ///
+ public bool ShouldBreak3 { get; set; }
+
+ ///
+ /// Gets or sets conditional break point 4.
+ ///
+ public bool ShouldBreak4 { get; set; }
+
+ ///
+ /// Gets or sets conditional break point 5.
+ ///
+ public bool ShouldBreak5 { get; set; }
+
+ // ===
+
+ public static readonly PdfSharpDebug Instance = new();
+
+ // ===
+
+ long MakeId(int objectNumber, int generationNumber) => objectNumber << 16 + generationNumber;
+
+ long MakeId(PdfObjectID id) => MakeId(id.ObjectNumber, id.GenerationNumber);
+
+ static readonly Dictionary ObjectIdSet = [];
+
+ static readonly Dictionary ItemNumberSet = [];
+
+ static readonly Dictionary DeadContainers = [];
+ }
+}
+#endif
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/PdfSharpCore.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/PdfSharpCore.cs
index 98fde181..51c6749d 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/PdfSharpCore.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/PdfSharpCore.cs
@@ -1,8 +1,9 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using PdfSharp.Fonts;
using PdfSharp.Internal;
+using PdfSharp.Fonts;
+using PdfSharp.Internal.OpenType;
using PdfSharp.Logging;
namespace PdfSharp.Diagnostics
@@ -15,26 +16,29 @@ public static class PdfSharpCore
///
/// Resets PDFsharp to a state equivalent to the state after
/// the assemblies are loaded.
+ /// Intended for unit testing only.
///
public static void ResetAll()
{
Capabilities.ResetAll();
GlobalFontSettings.ResetAll();
PdfSharpLogHost.ResetLogging();
- Globals.Global.RecreateGlobals();
+ PsGlobals.RecreatePsGlobals();
+ OtGlobals.RecreateOtGlobals();
- if (FontFactory.HasFontSources)
- throw new InvalidOperationException("Internal error.");
+ //if (FontFactory.HasFontSources)
+ // throw new InvalidOperationException("Internal error.");
}
///
/// Resets the font management equivalent to the state after
/// the assemblies are loaded.
+ /// Intended for unit testing only.
///
public static void ResetFontManagement()
{
GlobalFontSettings.ResetAll(true);
- Globals.Global.IncrementVersion();
+ PsGlobals.Global.IncrementVersion();
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs
deleted file mode 100644
index ce495960..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs
+++ /dev/null
@@ -1,362 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using Microsoft.Extensions.Logging;
-using PdfSharp.Logging;
-using PdfSharp.Pdf;
-
-namespace PdfSharp.Drawing
-{
- ///
- /// This interface will be implemented by specialized classes, one for JPEG, one for BMP, one for PNG, one for GIF. Maybe more.
- ///
- interface IImageImporter
- {
- ///
- /// Imports the image. Returns null if the image importer does not support the format.
- ///
- ImportedImage? ImportImage(StreamReaderHelper stream);
-
- ///
- /// Prepares the image data needed for the PDF file.
- ///
- ImageData PrepareImage(ImagePrivateData data);
- }
-
- ///
- /// Helper for dealing with Stream data.
- ///
- class StreamReaderHelper : IDisposable
- {
- internal StreamReaderHelper(byte[] data)
- {
- OriginalStream = null!;
- Data = data;
- Length = data.Length;
- }
-
- internal StreamReaderHelper(Stream stream, int streamLength)
- {
- OriginalStream = stream;
-
- if (stream is MemoryStream ms)
- {
- // If the given stream is a MemoryStream, work with it.
- if (ms.TryGetBuffer(out var buffer))
- {
- // Buffer is accessible - use it.
- Data = buffer.Array ?? throw new ArgumentNullException(nameof(stream), "Stream has no content byte array.");
- Length = (int)ms.Length;
- }
- else
- {
- // Buffer of given stream is not accessible, so read stream into new buffer.
- OwnedMemoryStream = new(streamLength);
- stream.CopyTo(OwnedMemoryStream);
- Data = OwnedMemoryStream.GetBuffer();
- Length = (int)OwnedMemoryStream.Length;
- PdfSharpLogHost.Logger.LogWarning("LoadImage: MemoryStream with buffer that is not publicly visible was used. " +
- "For better performance, set 'publiclyVisible' to true when creating the MemoryStream.");
- }
- }
- else
- {
- // If the given stream is not a MemoryStream, copy the stream to a new MemoryStream.
- if (streamLength > -1)
- {
- // Simple case: length of stream is known, create a MemoryStream with correct buffer size.
- OwnedMemoryStream = new(streamLength);
- stream.CopyTo(OwnedMemoryStream);
- Data = OwnedMemoryStream.GetBuffer();
- Length = (int)OwnedMemoryStream.Length;
- }
- else
- {
- // Complex case: length of stream is not known.
- // This only occurs with streams that do not support the Length property.
- OwnedMemoryStream = new();
- stream.CopyTo(OwnedMemoryStream);
- Data = OwnedMemoryStream.GetBuffer();
- Length = (int)OwnedMemoryStream.Length;
- // If buffer is larger than needed, create a new buffer with required size.
- if (Data.Length > Length)
- {
- var tmp = new Byte[Length];
- Buffer.BlockCopy(Data, 0, tmp, 0, Length);
- Data = tmp;
- }
- }
- }
- }
-
- internal byte GetByte(int offset)
- {
- if (CurrentOffset + offset >= Length)
- throw new InvalidOperationException("Index out of range.");
-
- return Data[CurrentOffset + offset];
- }
-
- internal ushort GetWord(int offset, bool bigEndian)
- {
- if (CurrentOffset + offset + 1 >= Length)
- throw new InvalidOperationException("Index out of range.");
-
- return (ushort)(bigEndian
- ? (Data[CurrentOffset + offset++] << 8) + Data[CurrentOffset + offset]
- : Data[CurrentOffset + offset++] + (Data[CurrentOffset + offset] << 8));
- }
-
- internal uint GetDWord(int offset, bool bigEndian)
- {
- if (CurrentOffset + offset + 3 >= Length)
- throw new InvalidOperationException("Index out of range.");
-
- // Are you a good developer?
- // What’s wrong with this code?
- //return (bigEndian
- // ? ((uint)Data[CurrentOffset + offset++] << 24) + ((uint)Data[CurrentOffset + offset++] << 16)
- // + ((uint)Data[CurrentOffset + offset++] << 8) + Data[CurrentOffset + offset]
- // : Data[CurrentOffset + offset++] + ((uint)Data[CurrentOffset + offset++] << 8))
- // + ((uint)Data[CurrentOffset + offset++] << 16) + ((uint)Data[CurrentOffset + offset] << 24);
- return (uint)(bigEndian
- ? (Data[CurrentOffset + offset++] << 24) +
- (Data[CurrentOffset + offset++] << 16) +
- (Data[CurrentOffset + offset++] << 8) +
- Data[CurrentOffset + offset]
- : Data[CurrentOffset + offset++] +
- (Data[CurrentOffset + offset++] << 8) +
- (Data[CurrentOffset + offset++] << 16) +
- (Data[CurrentOffset + offset] << 24));
- }
-
- ///
- /// Resets this instance.
- ///
- public void Reset() => CurrentOffset = 0;
-
- ///
- /// Gets the original stream.
- ///
- public Stream OriginalStream { get; }
-
- internal int CurrentOffset { get; set; }
-
- ///
- /// Gets the data as byte[].
- ///
- public byte[] Data { get; }
-
- ///
- /// Gets the length of Data.
- ///
- public int Length { get; }
-
-#if CORE || GDI || WPF
- ///
- /// Gets the owned memory stream. Can be null if no MemoryStream was created.
- ///
- public MemoryStream? OwnedMemoryStream { get; private set; }
-#endif
-
- public void Dispose()
- {
-#if CORE || GDI || WPF
- OwnedMemoryStream?.Dispose();
- OwnedMemoryStream = null;
-#endif
- }
- }
-
- ///
- /// The imported image.
- ///
- abstract class ImportedImage
- {
- ///
- /// Initializes a new instance of the class.
- ///
- protected ImportedImage(ImagePrivateData? data)
- {
- Data = data;
- if (data != null)
- data.Image = this;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- protected ImportedImage()
- : this(null)
- { }
-
- ///
- /// Gets information about the image.
- ///
- public ImageInformation Information { get; private set; } = new ImageInformation();
-
-#if true
- ///
- /// Gets the image data needed for the PDF file.
- ///
- // Data is created on demand without internal caching.
- public ImageData ImageData(PdfDocumentOptions options)
- {
- return PrepareImageData(options);
- }
-#else
- ///
- /// Gets the image data needed for the PDF file.
- ///
- public ImageData ImageData
- {
- get { if(!HasImageData) _imageData = PrepareImageData(); return _imageData; }
- private set => _imageData = value;
- }
- ImageData _imageData;
-
- ///
- /// Gets a value indicating whether image data for the PDF file was already prepared.
- ///
- public bool HasImageData => _imageData != null;
-#endif
-
- internal virtual ImageData PrepareImageData(PdfDocumentOptions options)
- {
- throw new NotImplementedException();
- }
-
- //IImageImporter _importer;
- internal ImagePrivateData? Data;
- }
-
- ///
- /// Public information about the image, filled immediately.
- /// Note: The stream will be read and decoded on the first call to PrepareImageData().
- /// ImageInformation can be filled for corrupted images that will throw an exception on PrepareImageData().
- ///
- class ImageInformation
- {
- internal enum ImageFormats
- {
- // ReSharper disable InconsistentNaming
- ///
- /// Value not set.
- ///
- Undefined = -1,
-
- ///
- /// Standard JPEG format (RGB).
- ///
- JPEG,
-
- ///
- /// Gray-scale JPEG format.
- ///
- JPEGGRAY,
-
- ///
- /// JPEG file with inverted CMYK, thus RGBW.
- ///
- JPEGRGBW,
-
- ///
- /// JPEG file with CMYK.
- ///
- JPEGCMYK,
-
- Palette1,
- Palette4,
- Palette8,
- Grayscale8,
- RGB24,
- ARGB32
-
- // ReSharper restore InconsistentNaming
- }
-
- internal ImageFormats ImageFormat = ImageFormats.Undefined;
-
- ///
- /// The width of the image in pixel.
- ///
- internal uint Width;
-
- ///
- /// The height of the image in pixel.
- ///
- internal uint Height;
-
- ///
- /// The horizontal DPI (dots per inch). Can be 0 if not supported by the image format.
- /// Note: JFIF (JPEG) files may contain either DPI or DPM or just the aspect ratio. Windows BMP files will contain DPM. Other formats may support any combination, including none at all.
- ///
- internal double HorizontalDPI;
-
- ///
- /// The vertical DPI (dots per inch). Can be 0 if not supported by the image format.
- ///
- internal double VerticalDPI;
-
- ///
- /// The horizontal DPM (dots per meter). Can be 0 if not supported by the image format.
- ///
- internal double HorizontalDPM;
-
- ///
- /// The vertical DPM (dots per meter). Can be 0 if not supported by the image format.
- ///
- internal double VerticalDPM;
-
- ///
- /// The horizontal component of the aspect ratio. Can be 0 if not supported by the image format.
- /// Note: Aspect ratio will be set if either DPI or DPM was set but may also be available in the absence of both DPI and DPM.
- ///
- internal double HorizontalAspectRatio;
-
- ///
- /// The vertical component of the aspect ratio. Can be 0 if not supported by the image format.
- ///
- internal double VerticalAspectRatio;
-
- ///
- /// The bit count per pixel. Only valid for certain images, will be 0 otherwise.
- ///
- internal uint BitCount;
-
- ///
- /// The colors used. Only valid for images with palettes, will be 0 otherwise.
- ///
- internal uint ColorsUsed;
-
- ///
- /// The default DPI (dots per inch) for images that do not have DPI information.
- ///
- internal double DefaultDPI;
- }
-
- ///
- /// Contains internal data. This includes a reference to the Stream if data for PDF was not yet prepared.
- ///
- abstract class ImagePrivateData
- {
- internal ImagePrivateData()
- { }
-
- ///
- /// Gets the image.
- ///
- public ImportedImage Image
- {
- get => _image ?? NRT.ThrowOnNull();
- internal set => _image = value;
- }
- ImportedImage? _image;
- }
-
- ///
- /// Contains data needed for PDF. Will be prepared when needed.
- ///
- abstract class ImageData
- { }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporter.cs
deleted file mode 100644
index 208169fb..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporter.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using System.IO;
-
-namespace PdfSharp.Drawing.Internal
-{
- ///
- /// The class that imports images of various formats.
- ///
- class ImageImporter
- {
- // TODO_OLD Make a singleton!
- ///
- /// Gets the image importer.
- ///
- public static ImageImporter GetImageImporter() // StL: To what kind of design pattern this function identifies itself?
- {
- return new ImageImporter();
- }
-
- ImageImporter()
- {
- _importers.Add(new ImageImporterJpeg());
- _importers.Add(new ImageImporterBmp());
- _importers.Add(new ImageImporterPng());
- // TODO_OLD: Special importer for PDF? Or dealt with at a higher level?
- }
-
- ///
- /// Imports the image.
- ///
- public ImportedImage? ImportImage(Stream stream)
- {
- long length = -1;
- try
- {
- length = stream.Length;
- }
- catch (NotSupportedException)
- {
- // We eat this exception.
- // We can handle streams that do not return their length.
- }
- catch (Exception ex)
- {
- // Unexpected exception.
- throw new InvalidOperationException("Cannot determine the length of the stream. Use a stream that supports the Length property. Consider copying the image to a MemoryStream.", ex);
- }
-
- if (length < -1 || length > Int32.MaxValue)
- throw new InvalidOperationException($"Image files with a size of {length} bytes are not supported. Use image files smaller than 2 GiB.");
-
- using var helper = new StreamReaderHelper(stream, (int)length);
- return TryImageImport(helper);
- }
-
-#if GDI || WPF || CORE
- ///
- /// Imports the image.
- ///
- public ImportedImage? ImportImage(string filename)
- {
- var data = File.ReadAllBytes(filename);
-
- using var helper = new StreamReaderHelper(data);
- return TryImageImport(helper);
- }
-
- ImportedImage? TryImageImport(StreamReaderHelper helper)
- {
- // Try all registered importers to see if any of them can handle the image.
- foreach (var importer in _importers)
- {
- helper.Reset();
- var image = importer.ImportImage(helper);
- if (image != null)
- return image;
- }
- return null;
- }
-#endif
- readonly List _importers = [];
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterBmp.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterBmp.cs
deleted file mode 100644
index a7e6c25b..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterBmp.cs
+++ /dev/null
@@ -1,636 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using PdfSharp.Pdf;
-using PdfSharp.Pdf.Advanced;
-using static PdfSharp.Drawing.ImageInformation;
-
-namespace PdfSharp.Drawing.Internal
-{
- class ImageImporterBmp : ImageImporterRoot, IImageImporter
- {
- public ImportedImage? ImportImage(StreamReaderHelper stream)
- {
- try
- {
- stream.CurrentOffset = 0;
- if (TestBitmapFileHeader(stream, out var offsetImageData))
- {
- // Note: TestBitmapFileHeader updates stream.CurrentOffset on success.
-
- ImagePrivateDataBitmap ipd = new ImagePrivateDataBitmap(stream.Data, stream.Length);
- ImportedImage ii = new ImportedImageBitmap(ipd);
- ii.Information.DefaultDPI = 96; // Assume 96 DPI if information not provided in the file.
-
- if (TestBitmapInfoHeader(stream, ii, offsetImageData))
- {
- return ii;
- }
- }
- }
- // ReSharper disable once EmptyGeneralCatchClause
- catch (Exception)
- {
- // Eat exceptions to have this image importer skipped.
- // We try to find an image importer that can handle the image.
- }
- return null;
- }
-
- bool TestBitmapFileHeader(StreamReaderHelper stream, out int offset)
- {
- offset = 0;
- // File must start with "BM".
- if (stream.GetWord(0, true) == 0x424d)
- {
- int filesize = (int)stream.GetDWord(2, false);
- // Integrity check: filesize set in BM header should match size of the stream.
- // We test "<" instead of "!=" to allow extra bytes at the end of the stream.
- if (filesize < stream.Length)
- return false;
-
- offset = (int)stream.GetDWord(10, false);
- stream.CurrentOffset += 14;
- return true;
- }
- return false;
- }
-
- bool TestBitmapInfoHeader(StreamReaderHelper stream, ImportedImage ii, int offset)
- {
- int size = (int)stream.GetDWord(0, false);
- if (size is 40 or 108 or 124) // sizeof BITMAPINFOHEADER == 40, sizeof BITMAPV4HEADER == 108, sizeof BITMAPV5HEADER == 124
- {
- uint width = stream.GetDWord(4, false);
- int height = (int)stream.GetDWord(8, false);
- int planes = stream.GetWord(12, false);
- uint bitCount = stream.GetWord(14, false);
- int compression = (int)stream.GetDWord(16, false);
- int sizeImage = (int)stream.GetDWord(20, false);
- int xPelsPerMeter = (int)stream.GetDWord(24, false);
- int yPelsPerMeter = (int)stream.GetDWord(28, false);
- uint colorsUsed = stream.GetDWord(32, false);
- uint colorsImportant = stream.GetDWord(36, false);
- // TODO_OLD Integrity and plausibility checks.
- if (sizeImage != 0 && sizeImage + offset > stream.Length)
- return false;
-
- var privateData = (ImagePrivateDataBitmap?)ii.Data;
-
- // Return true only for supported formats.
- if (compression is 0 or 3) // BI_RGB == 0, BI_BITFIELDS == 3
- {
- var data = (ImagePrivateDataBitmap?)ii.Data ?? NRT.ThrowOnNull();
- //((ImagePrivateDataBitmap)ii.Data).Offset = offset;
- //((ImagePrivateDataBitmap)ii.Data).ColorPaletteOffset = stream.CurrentOffset + size;
- data.Offset = offset;
- data.ColorPaletteOffset = stream.CurrentOffset + size;
- ii.Information.BitCount = bitCount;
- ii.Information.Width = width;
- ii.Information.Height = (uint)Math.Abs(height);
- ii.Information.HorizontalDPM = xPelsPerMeter;
- ii.Information.VerticalDPM = yPelsPerMeter;
- if (privateData == null)
- NRT.ThrowOnNull();
- privateData.FlippedImage = height < 0;
- if (planes == 1 && bitCount == 24)
- {
- // RGB24
- ii.Information.ImageFormat = ImageInformation.ImageFormats.RGB24;
-
- // TODO_OLD: Verify Mask if size >= 108 && compression == 3.
- return true;
- }
- if (planes == 1 && bitCount == 32)
- {
- // ARGB32
- //ii.Information.ImageFormat = ImageInformation.ImageFormats.ARGB32;
- ii.Information.ImageFormat = compression == 0 ?
- ImageInformation.ImageFormats.RGB24 :
- ImageInformation.ImageFormats.ARGB32;
-
- // TODO_OLD: tell RGB from ARGB. Idea: assume RGB if alpha is always 0.
-
- // Verify Mask if size >= 108 && compression == 3.
- if (size >= 108 && compression == 3)
- {
- uint maskRed = stream.GetDWord(40, false);
- uint maskGreen = stream.GetDWord(44, false);
- uint maskBlue = stream.GetDWord(48, false);
- uint maskAlpha = stream.GetDWord(52, false);
- // Reject images that do not have the expected byte order.
- if (maskRed != 0xff000000 ||
- maskGreen != 0x00ff0000 ||
- maskBlue != 0x0000ff00 ||
- maskAlpha != 0x000000ff)
- {
- // Not yet supported.
- return false;
- }
- }
- return true;
- }
- if (planes == 1 && bitCount == 8)
- {
- // Palette8
- ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette8;
- ii.Information.ColorsUsed = colorsUsed;
-
- return true;
- }
- if (planes == 1 && bitCount == 4)
- {
- // Palette4
- ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette4;
- ii.Information.ColorsUsed = colorsUsed;
-
- return true;
- }
- if (planes == 1 && bitCount == 1)
- {
- // Palette1
- ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette1;
- ii.Information.ColorsUsed = colorsUsed;
-
- return true;
- }
- // TODO_OLD Implement more formats!
- }
- }
- return false;
- }
-
- public ImageData PrepareImage(ImagePrivateData data)
- {
- throw new NotImplementedException();
- }
- }
-
- ///
- /// Bitmap refers to the format used in PDF. Will be used for BMP, PNG, TIFF, GIF, and others.
- ///
- class ImportedImageBitmap : ImportedImage
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public ImportedImageBitmap(ImagePrivateDataBitmap data)
- : base(data)
- { }
-
- internal override ImageData PrepareImageData(PdfDocumentOptions options)
- {
- ImagePrivateDataBitmap data = (ImagePrivateDataBitmap?)Data ?? NRT.ThrowOnNull();
- ImageDataBitmap imageData = new ImageDataBitmap(options);
- //imageData.Data = data.Data;
- //imageData.Length = data.Length;
-
- data.CopyBitmap(imageData, options);
-
- return imageData;
- }
- }
-
- ///
- /// Contains data needed for PDF. Will be prepared when needed.
- /// Bitmap refers to the format used in PDF. Will be used for BMP, PNG, TIFF, GIF, and others.
- ///
- class ImageDataBitmap : ImageData
- {
- internal ImageDataBitmap(PdfDocumentOptions options)
- {
- Options = options;
- }
-
- internal ImageDataBitmap(byte[] data, byte[] mask)
- {
- Data = data;
- Length = Data.Length;
- AlphaMask = mask;
- AlphaMaskLength = AlphaMask?.Length ?? 0;
- // TODO_OLD Bitmap mask?
- }
-
- ///
- /// Gets the data.
- ///
- public byte[] Data { get; internal set; } = null!;
-
- ///
- /// Gets the length.
- ///
- public int Length { get; internal set; }
-
- ///
- /// Gets the data for the CCITT format.
- ///
- public byte[]? DataFax { get; internal set; } = null;
-
- ///
- /// Gets the length.
- ///
- public int LengthFax { get; internal set; }
-
- public byte[] AlphaMask { get; internal set; } = null!;
-
- public int AlphaMaskLength { get; internal set; }
-
- public byte[] BitmapMask { get; internal set; } = null!;
-
- public int BitmapMaskLength { get; internal set; }
-
- public byte[] PaletteData { get; set; } = null!;
-
- public int PaletteDataLength { get; set; }
-
- public bool SegmentedColorMask;
-
- public int IsBitonal;
-
- public int K;
-
- public bool IsGray;
-
- internal readonly PdfDocumentOptions? Options;
- }
-
- ///
- /// Image data needed for Windows bitmap images.
- ///
- class ImagePrivateDataBitmap : ImagePrivateData
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public ImagePrivateDataBitmap(byte[] data, int length)
- {
- _data = data;
- _length = length;
- }
-
- ///
- /// Gets the data.
- ///
- public byte[] Data => _data;
-
- readonly byte[] _data;
-
- ///
- /// Gets the length.
- ///
- public int Length => _length;
-
- readonly int _length;
-
- ///
- /// True if first line is the top line, false if first line is the bottom line of the image. When needed, lines will be reversed while converting data into PDF format.
- ///
- internal bool FlippedImage;
-
- ///
- /// The offset of the image data in Data.
- ///
- internal int Offset;
-
- ///
- /// The offset of the color palette in Data.
- ///
- internal int ColorPaletteOffset;
-
- internal void CopyBitmap(ImageDataBitmap dest, PdfDocumentOptions options)
- {
- switch (Image?.Information.ImageFormat ?? NRT.ThrowOnNull())
- {
- case ImageInformation.ImageFormats.ARGB32:
- CopyTrueColorMemoryBitmap(4, 8, true, dest);
- break;
-
- case ImageInformation.ImageFormats.RGB24:
- if (this.Image.Information.BitCount == 32)
- CopyTrueColorMemoryBitmap(4, 8, false, dest);
- else
- CopyTrueColorMemoryBitmap(3, 8, false, dest);
- break;
-
- case ImageInformation.ImageFormats.Palette8:
- CopyIndexedMemoryBitmap(8, false, dest, options);
- break;
-
- case ImageInformation.ImageFormats.Palette4:
- CopyIndexedMemoryBitmap(4, false, dest, options);
- break;
-
- case ImageInformation.ImageFormats.Palette1:
- CopyIndexedMemoryBitmap(1, false, dest, options);
- break;
-
- default:
- throw new NotImplementedException();
- }
- }
-
- ///
- /// Copies images without color palette.
- ///
- /// 4 (32bpp RGB), 3 (24bpp RGB, 32bpp ARGB)
- /// 8
- /// true (ARGB), false (RGB)
- /// Destination
- void CopyTrueColorMemoryBitmap(int components, int bits, bool hasAlpha, ImageDataBitmap dest)
- {
- int width = (int)Image.Information.Width;
- int height = (int)Image.Information.Height;
-
- int logicalComponents = components;
- if (components == 4)
- logicalComponents = 3;
-
- byte[] imageData = new byte[logicalComponents * width * height];
-
- bool hasMask = false;
- bool hasAlphaMask = false;
- byte[]? alphaMask = hasAlpha ? new byte[width * height] : null;
- MonochromeMask? mask = hasAlpha ?
- new MonochromeMask(width, height) : null;
-
- int nFileOffset = Offset;
- int nOffsetRead = 0;
- if (logicalComponents == 3)
- {
- if (hasAlpha)
- {
- // Improvement: Get byte order for V5 bitmaps from bitmap header.
- // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header
- // "If the bV5Compression member of the BITMAPV5HEADER is BI_BITFIELDS, the bmiColors member contains three DWORD color masks that specify the red, green, and blue components of each pixel. Each DWORD in the bitmap array represents a single pixel."
-
- // Experimental: Expected BGRA, but found ARGB.
- ++nFileOffset;
- }
-
- for (int y = 0; y < height; ++y)
- {
- // TODO_OLD Handle Flipped.
- int nOffsetWrite = 3 * (height - 1 - y) * width;
- int nOffsetWriteAlpha = 0;
- if (hasAlpha)
- {
- mask!.StartLine(y); // NRT
- nOffsetWriteAlpha = (height - 1 - y) * width;
- }
-
- for (int x = 0; x < width; ++x)
- {
- imageData[nOffsetWrite] = Data[nFileOffset + nOffsetRead + 2];
- imageData[nOffsetWrite + 1] = Data[nFileOffset + nOffsetRead + 1];
- imageData[nOffsetWrite + 2] = Data[nFileOffset + nOffsetRead];
- if (hasAlpha)
- {
- //byte maskValue = Data[nFileOffset + nOffsetRead + 3]; // Expected, but does not always work.
- byte maskValue = Data[nFileOffset + nOffsetRead - 1]; // Experimental. See above.
- mask!.AddPel(maskValue);
- alphaMask![nOffsetWriteAlpha] = maskValue;
- if (!hasMask || !hasAlphaMask)
- {
- if (maskValue != 255)
- {
- hasMask = true;
- if (maskValue != 0)
- hasAlphaMask = true;
- }
- }
- ++nOffsetWriteAlpha;
- }
- nOffsetRead += hasAlpha ? 4 : components;
- nOffsetWrite += 3;
- }
- nOffsetRead = 4 * ((nOffsetRead + 3) / 4); // Align to 32 bit boundary
- }
- }
- else if (components == 1)
- {
- // Grayscale
- throw new NotImplementedException("Image format not supported (grayscales).");
- }
-
- dest.Data = imageData;
- dest.Length = imageData.Length;
-
- if (alphaMask != null && hasAlphaMask)
- {
- dest.AlphaMask = alphaMask;
- dest.AlphaMaskLength = alphaMask.Length;
- }
-
- if (mask != null && hasMask)
- {
- dest.BitmapMask = mask.MaskData;
- dest.BitmapMaskLength = mask.MaskData.Length;
- }
- }
-
- void CopyIndexedMemoryBitmap(int bits, bool checkTransparency, ImageDataBitmap dest, PdfDocumentOptions options)
- {
- int firstMaskColor = -1, lastMaskColor = -1;
- bool segmentedColorMask = false;
-
- int bytesColorPaletteOffset = ((ImagePrivateDataBitmap)Image.Data!).ColorPaletteOffset; // GDI+ always returns Windows bitmaps: sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER
-
- int bytesFileOffset = ((ImagePrivateDataBitmap)Image.Data).Offset;
- uint paletteColors = Image.Information.ColorsUsed;
- int width = (int)Image.Information.Width;
- int height = (int)Image.Information.Height;
-
- MonochromeMask? mask = checkTransparency ? new MonochromeMask(width, height) : null;
-
- bool isGray = bits == 8 && (paletteColors == 256 || paletteColors == 0);
- int isBitonal = 0; // 0: false; >0: true; <0: true (inverted)
- byte[] paletteData = new byte[3 * paletteColors];
- for (int color = 0; color < paletteColors; ++color)
- {
- paletteData[3 * color] = Data[bytesColorPaletteOffset + 4 * color + 2];
- paletteData[3 * color + 1] = Data[bytesColorPaletteOffset + 4 * color + 1];
- paletteData[3 * color + 2] = Data[bytesColorPaletteOffset + 4 * color + 0];
- if (isGray)
- isGray = paletteData[3 * color] == paletteData[3 * color + 1] &&
- paletteData[3 * color] == paletteData[3 * color + 2];
-
- if (checkTransparency && Data[bytesColorPaletteOffset + 4 * color + 3] < 128)
- {
- // We treat this as transparency:
- if (firstMaskColor == -1)
- firstMaskColor = color;
- if (lastMaskColor == -1 || lastMaskColor == color - 1)
- lastMaskColor = color;
- if (lastMaskColor != color)
- segmentedColorMask = true;
- }
- //else
- //{
- // // We treat this as opacity:
- //}
- }
-
- if (bits == 1)
- {
- if (paletteColors == 0)
- isBitonal = 1;
- if (paletteColors == 2)
- {
- if (paletteData[0] == 0 &&
- paletteData[1] == 0 &&
- paletteData[2] == 0 &&
- paletteData[3] == 255 &&
- paletteData[4] == 255 &&
- paletteData[5] == 255)
- isBitonal = 1; // Black on white
- if (paletteData[5] == 0 &&
- paletteData[4] == 0 &&
- paletteData[3] == 0 &&
- paletteData[2] == 255 &&
- paletteData[1] == 255 &&
- paletteData[0] == 255)
- isBitonal = -1; // White on black
- }
- }
-
- // NYI: (no sample found where this was required)
- // if (segmentedColorMask = true)
- // { ... }
-
- bool isFaxEncoding = false;
- byte[] imageData = new byte[((width * bits + 7) / 8) * height];
- byte[] imageDataFax = null!;
- int k = 0;
-
- if (bits == 1 && options.EnableCcittCompressionForBilevelImages)
- {
- // TODO_OLD: flag/option?
- // We try Group 3 1D and Group 4 (2D) encoding here and keep the smaller byte array.
- //byte[] temp = new byte[imageData.Length];
- //int ccittSize = DoFaxEncoding(ref temp, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height);
-
- // It seems that Group 3 2D encoding never beats both other encodings, therefore we don’t call it here.
- //byte[] temp2D = new byte[imageData.Length];
- //uint dpiY = (uint)image.VerticalResolution;
- //uint kTmp = 0;
- //int ccittSize2D = DoFaxEncoding2D((uint)bytesFileOffset, ref temp2D, imageBits, (uint)width, (uint)height, dpiY, out kTmp);
- //k = (int) kTmp;
-
- byte[] tempG4 = new byte[imageData.Length];
- int ccittSizeG4 = PdfImage.DoFaxEncodingGroup4(ref tempG4, Data, (uint)bytesFileOffset, (uint)width, (uint)height);
-
- isFaxEncoding = /*ccittSize > 0 ||*/ ccittSizeG4 > 0;
- if (isFaxEncoding)
- {
- //if (ccittSize == 0)
- // ccittSize = 0x7fffffff;
- if (ccittSizeG4 == 0)
- ccittSizeG4 = 0x7fffffff;
- //if (ccittSize <= ccittSizeG4)
- //{
- // Array.Resize(ref temp, ccittSize);
- // imageDataFax = temp;
- // k = 0;
- //}
- //else
- {
- Array.Resize(ref tempG4, ccittSizeG4);
- imageDataFax = tempG4;
- k = -1;
- }
- }
- }
-
- //if (!isFaxEncoding)
- {
- int bytesOffsetRead = 0;
- if (bits == 8 || bits == 4 || bits == 1)
- {
- int bytesPerLine = (width * bits + 7) / 8;
- for (int y = 0; y < height; ++y)
- {
- mask?.StartLine(y);
- int bytesOffsetWrite = (height - 1 - y) * ((width * bits + 7) / 8);
- for (int x = 0; x < bytesPerLine; ++x)
- {
- if (isGray)
- {
- // Lookup the gray value from the palette:
- imageData[bytesOffsetWrite] = paletteData[3 * Data[bytesFileOffset + bytesOffsetRead]];
- }
- else
- {
- // Store the palette index.
- imageData[bytesOffsetWrite] = Data[bytesFileOffset + bytesOffsetRead];
- }
- if (firstMaskColor != -1)
- {
- int n = Data[bytesFileOffset + bytesOffsetRead];
- if (bits == 8)
- {
- // TODO_OLD???: segmentedColorMask == true => bad mask NYI
- mask?.AddPel((n >= firstMaskColor) && (n <= lastMaskColor));
- }
- else if (bits == 4)
- {
- // TODO_OLD???: segmentedColorMask == true => bad mask NYI
- int n1 = (n & 0xf0) / 16;
- int n2 = (n & 0x0f);
- mask?.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor));
- mask?.AddPel((n2 >= firstMaskColor) && (n2 <= lastMaskColor));
- }
- else if (bits == 1)
- {
- // TODO_OLD???: segmentedColorMask == true => bad mask NYI
- for (int bit = 1; bit <= 8; ++bit)
- {
- int n1 = (n & 0x80) / 128;
- mask?.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor));
- n *= 2;
- }
- }
- }
- bytesOffsetRead += 1;
- bytesOffsetWrite += 1;
- }
- bytesOffsetRead = 4 * ((bytesOffsetRead + 3) / 4); // Align to 32 bit boundary
- }
- }
- else
- {
- throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3");
- }
- }
-
- dest.Data = imageData;
- dest.Length = imageData.Length;
-
- if (imageDataFax != null)
- {
- dest.DataFax = imageDataFax;
- dest.LengthFax = imageDataFax.Length;
- }
-
- dest.IsGray = isGray;
- dest.K = k;
- dest.IsBitonal = isBitonal;
-
- dest.PaletteData = paletteData;
- dest.PaletteDataLength = paletteData.Length;
- dest.SegmentedColorMask = segmentedColorMask;
-
- //if (alphaMask != null)
- //{
- // dest.AlphaMask = alphaMask;
- // dest.AlphaMaskLength = alphaMask.Length;
- //}
-
- if (mask != null && mask.MaskUsed && firstMaskColor != -1)
- {
- dest.BitmapMask = mask.MaskData;
- dest.BitmapMaskLength = mask.MaskData.Length;
- }
-
- }
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs
deleted file mode 100644
index 76330bac..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs
+++ /dev/null
@@ -1,461 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using System;
-using System.Text;
-using PdfSharp.Pdf;
-
-namespace PdfSharp.Drawing.Internal
-{
- // ReSharper disable once InconsistentNaming
- class ImageImporterJpeg : ImageImporterRoot, IImageImporter
- {
- // TODO_OLD Find information about JPEG2000.
-
- // Notes: JFIF is big-endian.
-
- public ImportedImage? ImportImage(StreamReaderHelper stream)
- {
- try
- {
- stream.CurrentOffset = 0;
- // Test 2 magic bytes.
- if (TestFileHeader(stream))
- {
- // Skip over 2 magic bytes.
- stream.CurrentOffset += 2;
-
- var ipd = new ImagePrivateDataDct(stream.Data, stream.Length);
- var ii = new ImportedImageJpeg(ipd);
- ii.Information.DefaultDPI = 72; // Assume 72 DPI if information not provided in the file.
- if (TestJfifHeader(stream, ii))
- {
- bool colorHeader = false, infoHeader = false;
-
- while (MoveToNextHeader(stream))
- {
- if (TestColorFormatHeader(stream, ii))
- {
- colorHeader = true;
- }
- else if (TestInfoHeader(stream, ii))
- {
- infoHeader = true;
- }
- }
- if (colorHeader && infoHeader)
- return ii;
- }
- }
- }
- // ReSharper disable once EmptyGeneralCatchClause
- catch (Exception)
- {
- // Eat exceptions to have this image importer skipped.
- // We try to find an image importer that can handle the image.
- }
- return null;
- }
-
- bool TestFileHeader(StreamReaderHelper stream)
- {
- // File must start with 0xffd8.
- return stream.GetWord(0, true) == 0xffd8;
- }
-
- bool TestJfifHeader(StreamReaderHelper stream, ImportedImage ii)
- {
- // Just in case the header is not the first block in the file, search through all blocks now.
- //var currentOffset = stream.CurrentOffset;
-
- bool header = TestJfifHeaderWorker(stream, ii) ||
- TestExifHeaderWorker(stream/*, ii*/) ||
- TestApp2HeaderWorker(stream) ||
- TestApp13HeaderWorker(stream);
-
- return header;
- }
-
- bool TestJfifHeaderWorker(StreamReaderHelper stream, ImportedImage ii)
- {
- // The App0 header should be the first header in every JFIF file.
- if (stream.GetWord(0, true) == 0xffe0)
- {
- // TODO_OLD Skip EXIF header if it precedes the JFIF header.
-
- // Now check for text "JFIF".
- if (stream.GetDWord(4, true) == 0x4a464946)
- {
- int blockLength = stream.GetWord(2, true);
- if (blockLength >= 16)
- {
- // ReSharper disable once UnusedVariable
- int version = stream.GetWord(9, true);
- int units = stream.GetByte(11);
- int densityX = stream.GetWord(12, true);
- int densityY = stream.GetWord(14, true);
-
- switch (units)
- {
- case 0: // Aspect ratio only.
- ii.Information.HorizontalAspectRatio = densityX;
- ii.Information.VerticalAspectRatio = densityY;
- break;
- case 1: // DPI.
- ii.Information.HorizontalDPI = densityX;
- ii.Information.VerticalDPI = densityY;
- break;
- case 2: // DPCM.
- ii.Information.HorizontalDPM = densityX * 100;
- ii.Information.VerticalDPM = densityY * 100;
- break;
- }
-
- // More information here? More tests?
- return true;
- }
- }
- }
- return false;
- }
-
- bool TestExifHeaderWorker(StreamReaderHelper stream/*, ImportedImage ii*/)
- {
- // The App0 header should be the first header in every JFIF file.
- if (stream.GetWord(0, true) == 0xffe1)
- {
- // Now check for text "Exif".
- if (stream.GetDWord(4, true) == 0x45786966)
- {
- // We expect 00 00 after Exif.
- int eos = stream.GetWord(8, true);
- // We expect MM or II.
- int ft = stream.GetWord(10, true);
- if (eos == 0 &&
- (ft == 0x4d4d || ft == 0x4949))
- {
- // More information here? More tests?
- // TODO_OLD Find JFIF header in the EXIF header.
- // EXIF headers are similar to TIFF.
- return true;
- }
- }
- }
- return false;
- }
-
- bool TestApp2HeaderWorker(StreamReaderHelper stream)
- {
- // Check for APP2 header.
- if (stream.GetWord(0, true) == 0xffe2)
- {
- int length = stream.GetWord(2, true);
-
- StringBuilder identifier = new();
- int idx = 4;
- do
- {
- byte c = stream.GetByte(idx);
- if (c == 0x00)
- {
- break;
- }
- identifier.Append((char)c);
- idx++;
- } while (idx < length);
-
- var id = identifier.ToString();
- if (!id.Equals("ICC_PROFILE"))
- {
- return false;
- }
-
- return true;
- }
- return false;
- }
-
- bool TestApp13HeaderWorker(StreamReaderHelper stream)
- {
- // Check for APP13 header.
- if (stream.GetWord(0, true) == 0xffed)
- {
- int length = stream.GetWord(2, true);
-
- StringBuilder identifier = new();
- int idx = 4;
- do
- {
- byte c = stream.GetByte(idx);
- if (c == 0x00)
- {
- break;
- }
- identifier.Append((char)c);
- idx++;
- } while (idx < length);
-
- var id = identifier.ToString();
- if (!id.StartsWith("Photoshop ") && !id.StartsWith("Adobe_Photoshop")) // "Photoshop 3.0", "Adobe_Photoshop2.5:", etc.
- {
- return false;
- }
-
- idx++;
- if (idx + 3 < length && stream.GetDWord(idx, true) == 0x3842494d) // 8BIM
- {
- return true;
- }
- }
- return false;
- }
-
- bool TestColorFormatHeader(StreamReaderHelper stream, ImportedImage ii)
- {
- // The SOS header (start of scan).
- if (stream.GetWord(0, true) == 0xffda)
- {
- int components = stream.GetByte(4);
- if (components < 1 || components > 4 || components == 2)
- return false;
- // 1 for grayscale, 3 for RGB, 4 for CMYK.
-
- int blockLength = stream.GetWord(2, true);
- // Integrity check: correct size?
- if (blockLength != 6 + 2 * components)
- return false;
-
- // Eventually do more tests here.
- // Info: we assume that all JPEG files with 4 components are RGBW (inverted CMYK) and not CMYK.
- // We add a test to tell CMYK from RGBW when we encounter a test file in CMYK format.
- var format = components == 3
- ? ImageInformation.ImageFormats.JPEG
- : (components == 1
- ? ImageInformation.ImageFormats.JPEGGRAY
- : ImageInformation.ImageFormats.JPEGRGBW);
- if (ii.Information.ImageFormat == ImageInformation.ImageFormats.Undefined)
- ii.Information.ImageFormat = format;
-#if DEBUG
- else
- {
- Debug.Assert(format == ii.Information.ImageFormat);
- }
-#endif
-
- return true;
- }
- return false;
- }
-
- bool TestInfoHeader(StreamReaderHelper stream, ImportedImage ii)
- {
- // The SOF header (start of frame).
- int header = stream.GetWord(0, true);
- if (header >= 0xffc0 && header <= 0xffc3 ||
- header >= 0xffc9 && header <= 0xffcb)
- {
- // Lines in image.
- int sizeY = stream.GetWord(5, true);
- // Samples per line.
- int sizeX = stream.GetWord(7, true);
-
- int components = stream.GetByte(9);
- if (components is 1 or 3 or 4)
- {
- // Info: we assume that all JPEG files with 4 components are RGBW (inverted CMYK) and not CMYK.
- // We add a test to tell CMYK from RGBW when we encounter a test file in CMYK format.
- var format = components == 3
- ? ImageInformation.ImageFormats.JPEG
- : (components == 1
- ? ImageInformation.ImageFormats.JPEGGRAY
- : ImageInformation.ImageFormats.JPEGRGBW);
-
- // We have more faith in the information from the SOF header than the SOS header.
-#if DEBUG
- // Assert information in Debug mode.
- if (ii.Information.ImageFormat != ImageInformation.ImageFormats.Undefined)
- {
- Debug.Assert(format == ii.Information.ImageFormat);
- }
-#endif
- // Always use information from SOF header. Just overwrite previously set format, maybe from SOS header.
- ii.Information.ImageFormat = format;
- }
-
- ii.Information.Width = (uint)sizeX;
- ii.Information.Height = (uint)sizeY;
-
- return true;
- }
- return false;
- }
-
- bool MoveToNextHeader(StreamReaderHelper stream)
- {
- int blockLength = stream.GetWord(2, true);
-
- int headerMagic = stream.GetByte(0);
- int headerType = stream.GetByte(1);
-
- if (headerMagic == 0xff)
- {
- // EOI: last header.
- if (headerType == 0xd9)
- return false;
-
- // Check for standalone markers.
- if (headerType == 0x01 || headerType >= 0xd0 && headerType <= 0xd7)
- {
- stream.CurrentOffset += 2;
- return true;
- }
-
- // Now assume header with block size.
- stream.CurrentOffset += 2 + blockLength;
- return true;
- }
- return false;
- }
-
- public ImageData PrepareImage(ImagePrivateData data)
- {
- throw new NotImplementedException();
- }
-
- //int GetJpgSizeTestCode(byte[] pData, uint FileSizeLow, out int pWidth, out int pHeight)
- //{
- // pWidth = -1;
- // pHeight = -1;
-
- // int i = 0;
-
- // if ((pData[i] == 0xFF) && (pData[i + 1] == 0xD8) && (pData[i + 2] == 0xFF) && (pData[i + 3] == 0xE0))
- // {
- // i += 4;
-
- // // Check for valid JPEG header (null terminated JFIF)
- // if ((pData[i + 2] == 'J') && (pData[i + 3] == 'F') && (pData[i + 4] == 'I') && (pData[i + 5] == 'F')
- // && (pData[i + 6] == 0x00))
- // {
-
- // //Retrieve the block length of the first block since the first block will not contain the size of file
- // int block_length = pData[i] * 256 + pData[i + 1];
-
- // while (i < FileSizeLow)
- // {
- // //Increase the file index to get to the next block
- // i += block_length;
-
- // if (i >= FileSizeLow)
- // {
- // //Check to protect against segmentation faults
- // return -1;
- // }
-
- // if (pData[i] != 0xFF)
- // {
- // return -2;
- // }
-
- // if (pData[i + 1] == 0xC0)
- // {
- // //0xFFC0 is the "Start of frame" marker which contains the file size
- // //The structure of the 0xFFC0 block is quite simple [0xFFC0][ushort length][uchar precision][ushort x][ushort y]
- // pHeight = pData[i + 5] * 256 + pData[i + 6];
- // pWidth = pData[i + 7] * 256 + pData[i + 8];
-
- // return 0;
- // }
- // else
- // {
- // i += 2; //Skip the block marker
-
- // //Go to the next block
- // block_length = pData[i] * 256 + pData[i + 1];
- // }
- // }
-
- // //If this point is reached then no size was found
- // return -3;
- // }
- // else
- // {
- // return -4;
- // } //Not a valid JFIF string
- // }
- // else
- // {
- // return -5;
- // } //Not a valid SOI header
-
- // //return -6;
- //} // GetJpgSize
- }
-
- ///
- /// Imported JPEG image.
- ///
- class ImportedImageJpeg : ImportedImage
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public ImportedImageJpeg(ImagePrivateDataDct data)
- : base(data)
- { }
-
- internal override ImageData PrepareImageData(PdfDocumentOptions options)
- {
- var data = (ImagePrivateDataDct?)Data ?? NRT.ThrowOnNull();
- var imageData = new ImageDataDct
- {
- Data = data.Data,
- Length = data.Length
- };
-
- return imageData;
- }
- }
-
- ///
- /// Contains data needed for PDF. Will be prepared when needed.
- ///
- class ImageDataDct : ImageData
- {
- ///
- /// Gets the data.
- ///
- public byte[] Data { get; internal set; } = null!;
-
- ///
- /// Gets the length.
- ///
- public int Length { get; internal set; }
- }
-
- /*internal*/
- ///
- /// Private data for JPEG images.
- ///
- class ImagePrivateDataDct : ImagePrivateData
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public ImagePrivateDataDct(byte[] data, int length)
- {
- Data = data;
- Length = length;
- }
-
- ///
- /// Gets the data.
- ///
- public byte[] Data { get; }
-
- ///
- /// Gets the length.
- ///
- public int Length { get; }
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterRoot.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterRoot.cs
deleted file mode 100644
index a3413db0..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterRoot.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-namespace PdfSharp.Drawing.Internal
-{
- abstract class ImageImporterRoot
- {
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Layout/XTextFormatter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Layout/XTextFormatter.cs
index bdb8b5f5..f0d6fe5a 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Layout/XTextFormatter.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Layout/XTextFormatter.cs
@@ -1,8 +1,6 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
-using System.Collections.Generic;
using PdfSharp.Pdf.IO;
namespace PdfSharp.Drawing.Layout
@@ -167,7 +165,7 @@ void CreateBlocks()
blockLength = 0;
_blocks.Add(new Block(BlockType.LineBreak));
}
- // The non-breaking space is whitespace, so we treat it like non-whitespace.
+ // The non-breaking space is whitespace, so we treat it like non-white-space.
else if (ch != Chars.NonBreakableSpace && Char.IsWhiteSpace(ch))
{
if (inNonWhiteSpace)
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs
index 69fcc3f7..fa894a01 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs
@@ -27,6 +27,11 @@ namespace PdfSharp.Drawing.Pdf
///
sealed class PdfGraphicsState(XGraphicsPdfRenderer renderer) : ICloneable
{
+ const string DefaultNumberFormat2 = Config.SignificantDecimalPlaces2;
+ const string DefaultNumberFormat3 = Config.SignificantDecimalPlaces3;
+ const string DefaultNumberFormat4 = Config.SignificantDecimalPlaces4;
+ const string DefaultNumberFormat7 = Config.SignificantDecimalPlaces7;
+
public PdfGraphicsState Clone()
{
var state = (PdfGraphicsState)MemberwiseClone();
@@ -37,7 +42,7 @@ public PdfGraphicsState Clone()
internal int Level;
- internal InternalGraphicsState InternalState = default!;
+ internal InternalGraphicsState InternalState = null!;
public void PushState()
{
@@ -58,14 +63,14 @@ public void PopState()
int _realizedLineJoin = -1;
double _realizedMiterLimit = -1;
XDashStyle _realizedDashStyle = (XDashStyle)(-1);
- string _realizedDashPattern = default!;
- XColor _realizedStrokeColor = XColor.Empty;
+ string _realizedDashPattern = null!;
+ XColor? _realizedStrokeColor; // Null means no stroke color has been realized yet.
bool _realizedStrokeOverPrint;
public void RealizePen(XPen pen, PdfColorMode colorMode)
{
- const string format = Config.SignificantDecimalPlaces3;
- const string format2 = Config.SignificantDecimalPlaces2;
+ const string format = DefaultNumberFormat3;
+ const string format2 = DefaultNumberFormat2;
XColor color = pen.Color;
bool overPrint = pen.Overprint;
@@ -165,7 +170,7 @@ public void RealizePen(XPen pen, PdfColorMode colorMode)
if (colorMode != PdfColorMode.Cmyk)
{
- if (_realizedStrokeColor.Rgb != color.Rgb)
+ if (_realizedStrokeColor == null || _realizedStrokeColor.Value.Rgb != color.Rgb)
{
renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb));
renderer.Append(" RG\n");
@@ -173,21 +178,25 @@ public void RealizePen(XPen pen, PdfColorMode colorMode)
}
else
{
- if (!ColorSpaceHelper.IsEqualCmyk(_realizedStrokeColor, color))
+ if (_realizedStrokeColor == null || !ColorSpaceHelper.IsEqualCmyk(_realizedStrokeColor.Value, color))
{
renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk));
renderer.Append(" K\n");
}
}
- if (renderer.Owner.Version >= 14 && (_realizedStrokeColor.A != color.A || _realizedStrokeOverPrint != overPrint))
+ if (renderer.Owner.Version >= 14 // CA and ca keys of PdfExtGState are supported since PDF version 1.4.
+ && (// Add PdfExtGState, if no stroke color has been realized yet...
+ _realizedStrokeColor == null
+ // or if alpha or overPrint value has changed.
+ || _realizedStrokeColor.Value.A != color.A || _realizedStrokeOverPrint != overPrint))
{
PdfExtGState extGState = renderer.Owner.ExtGStateTable.GetExtGStateStroke(color.A, overPrint);
string gs = renderer.Resources.AddExtGState(extGState);
renderer.AppendFormatString("{0} gs\n", gs);
// Must create transparency group.
- if (renderer._page != null! && color.A < 1)
+ if (renderer._page != null! && color.A < 1.0)
renderer._page.TransparencyUsed = true;
}
_realizedStrokeColor = color;
@@ -198,7 +207,7 @@ public void RealizePen(XPen pen, PdfColorMode colorMode)
#region Fill
- XColor _realizedFillColor = XColor.Empty;
+ XColor? _realizedFillColor; // Null means no fill color has been realized yet.
bool _realizedNonStrokeOverPrint;
public void RealizeBrush(XBrush brush, PdfColorMode colorMode, int renderingMode, double fontEmSize)
@@ -253,7 +262,7 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode)
if (colorMode != PdfColorMode.Cmyk)
{
- if (_realizedFillColor.IsEmpty || _realizedFillColor.Rgb != color.Rgb)
+ if (_realizedFillColor == null || _realizedFillColor.Value.Rgb != color.Rgb)
{
renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb));
renderer.Append(" rg\n");
@@ -263,16 +272,19 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode)
{
Debug.Assert(colorMode == PdfColorMode.Cmyk);
- if (_realizedFillColor.IsEmpty || !ColorSpaceHelper.IsEqualCmyk(_realizedFillColor, color))
+ if (_realizedFillColor == null || !ColorSpaceHelper.IsEqualCmyk(_realizedFillColor.Value, color))
{
renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk));
renderer.Append(" k\n");
}
}
- if (renderer.Owner.Version >= 14 && (_realizedFillColor.A != color.A || _realizedNonStrokeOverPrint != overPrint))
+ if (renderer.Owner.Version >= 14 // CA and ca keys of PdfExtGState are supported since PDF version 1.4.
+ && (// Add PdfExtGState, if no fill color has been realized yet...
+ _realizedFillColor == null
+ // or if alpha or overPrint value has changed.
+ || _realizedFillColor.Value.A != color.A || _realizedNonStrokeOverPrint != overPrint))
{
-
PdfExtGState extGState = renderer.Owner.ExtGStateTable.GetExtGStateNonStroke(color.A, overPrint);
string gs = renderer.Resources.AddExtGState(extGState);
renderer.AppendFormatString("{0} gs\n", gs);
@@ -288,7 +300,7 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode)
internal void RealizeNonStrokeTransparency(double transparency, PdfColorMode colorMode)
{
- XColor color = _realizedFillColor;
+ XColor color = _realizedFillColor ?? XColor.Empty; // Use empty color as origin if no fill color has been realized yet.
color.A = transparency;
RealizeFillColor(color, _realizedNonStrokeOverPrint, colorMode);
}
@@ -305,9 +317,11 @@ internal void RealizeNonStrokeTransparency(double transparency, PdfColorMode col
int _realizedRenderingMode; // Reference: TABLE 5.2 Text state operators / Page 398
double _realizedCharSpace; // Reference: TABLE 5.2 Text state operators / Page 398
+ internal string RealizedFontName => _realizedFontName;
+
public void RealizeFont(XGlyphTypeface glyphTypeface, double emSize, XBrush brush, int renderingMode, FontType fontType)
{
- const string format = Config.SignificantDecimalPlaces3;
+ const string format = DefaultNumberFormat3;
// So far rendering mode 0 (fill text) and 2 (fill, then stroke text) only.
RealizeBrush(brush, renderer._colorMode, renderingMode, emSize); // _renderer.page.document.Options.ColorMode);
@@ -342,26 +356,23 @@ public void RealizeFont(XGlyphTypeface glyphTypeface, double emSize, XBrush brus
string fontName = renderer.GetFontName(glyphTypeface, fontType, out _realizedFont);
if (fontName != _realizedFontName || _realizedFontSize != emSize)
{
- s_formatTf ??= "{0} {1:" + format + "} Tf\n";
+ const string formatTf = "{0} {1:" + format + "} Tf\n";
if (renderer.Gfx.PageDirection == XPageDirection.Downwards)
{
// earlier:
// renderer.AppendFormatFont("{0} {1:" + format + "} Tf\n", fontName, emSize);
- renderer.AppendFormatFont(s_formatTf, fontName, emSize);
+ renderer.AppendFormatFont(formatTf, fontName, emSize);
}
else
{
// earlier:
// renderer.AppendFormatFont("{0} {1:" + format + "} Tf\n", fontName, emSize);
- renderer.AppendFormatFont(s_formatTf, fontName, emSize);
+ renderer.AppendFormatFont(formatTf, fontName, emSize);
}
_realizedFontName = fontName;
_realizedFontSize = emSize;
}
}
- // ReSharper disable InconsistentNaming
- static string? s_formatTf;
- // ReSharper restore InconsistentNaming
public XPoint RealizedTextPosition;
@@ -432,8 +443,15 @@ public void AddTransform(XMatrix value, XMatrixOrder matrixOrder)
{
// Take chirality into account and
// invert the direction of rotation.
+#if true
+ // Prevent getting negative zeros in cm operator.
+ transform.M12 = value.M12 == 0.0 ? 0 : -value.M12;
+ transform.M21 = value.M21 == 0.0 ? 0 : -value.M21;
+#else
+ // Can result in e.g. "1 -0 -0 1 200 150 cm".
transform.M12 = -value.M12;
transform.M21 = -value.M21;
+#endif
}
UnrealizedCtm.Prepend(transform);
@@ -450,16 +468,16 @@ public void RealizeCtm()
{
Debug.Assert(!UnrealizedCtm.IsIdentity, "mrCtm is unnecessarily set.");
- const string format = Config.SignificantDecimalPlaces7;
- s_formatCtm ??= "{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:"
- + format + "} {5:" + format + "} cm\n";
+ const string format = DefaultNumberFormat7;
+ const string formatCtm =
+ "{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm\n";
double[] matrix = UnrealizedCtm.GetElements();
// Use up to six decimal digits to prevent round up problems.
// earlier:
// renderer.AppendFormatArgs("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm\n",
// matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
- renderer.AppendFormatArgs(s_formatCtm, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
+ renderer.AppendFormatArgs(formatCtm, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
RealizedCtm.Prepend(UnrealizedCtm);
UnrealizedCtm = new XMatrix();
@@ -469,7 +487,7 @@ public void RealizeCtm()
}
}
// ReSharper disable InconsistentNaming
- static string? s_formatCtm;
+ //static string? s_formatCtm;
// ReSharper restore InconsistentNaming
#endregion
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs
index 32a856c6..90e19214 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs
@@ -4,9 +4,14 @@
#define ITALIC_SIMULATION
using System.Text;
-using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
+using PdfSharp.Internal.OpenType;
using PdfSharp.Events;
using PdfSharp.Fonts.Internal;
+using PdfSharp.Pdf;
+using PdfSharp.Pdf.Internal;
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.IO;
#if GDI
using System.Drawing.Drawing2D;
#endif
@@ -20,29 +25,37 @@
using SysPoint = Windows.Foundation.Point;
using SysSize = Windows.Foundation.Size;
#endif
-using PdfSharp.Fonts.OpenType;
-using PdfSharp.Internal;
-using PdfSharp.Logging;
-using PdfSharp.Fonts;
-using PdfSharp.Pdf;
-using PdfSharp.Pdf.Internal;
-using PdfSharp.Pdf.Advanced;
-using PdfSharp.Pdf.IO;
// ReSharper disable RedundantNameQualifier
// ReSharper disable CompareOfFloatsByEqualityOperator
+namespace PdfSharp.Pdf // #FILE
+{
+ ///
+ /// Either XGraphics or DrawingContext object.
+ ///
+ internal interface IPageContentRenderer
+ {
+ // Just a marker interface.
+ }
+}
+
namespace PdfSharp.Drawing.Pdf
{
///
/// Represents a drawing surface for PdfPages.
///
- class XGraphicsPdfRenderer : IXGraphicsRenderer
+ class XGraphicsPdfRenderer : IXGraphicsRenderer, IPageContentRenderer
{
+ const string DefaultNumberFormat2 = Config.SignificantDecimalPlaces2;
+ const string DefaultNumberFormat3 = Config.SignificantDecimalPlaces3;
+ const string DefaultNumberFormat4 = Config.SignificantDecimalPlaces4;
+ const string DefaultNumberFormat7 = Config.SignificantDecimalPlaces7;
+
public XGraphicsPdfRenderer(PdfPage page, XGraphics gfx, XGraphicsPdfPageOptions options)
{
_page = page;
- _colorMode = page._document.Options.ColorMode;
+ _colorMode = page.Document.Options.ColorMode;
_options = options;
_gfx = gfx;
_content = new StringBuilder();
@@ -75,12 +88,30 @@ public void Close()
{
if (_page != null!)
{
- var content2 = _page.RenderContent!; // NRT
- content2.CreateStream(PdfEncoders.RawEncoding.GetBytes(GetContent()));
+ var renderer = _page.RenderContent?.Renderer;
+ if (!ReferenceEquals(renderer, this))
+ {
+ throw new InvalidOperationException(
+ "This XGraphicsPdfRenderer is not the owner of currently rendered content stream. " +
+ "Maybe a PDFsharp Graphics DrawingContext is still open.");
+ }
+ var content = _page.RenderContent;
+ Debug.Assert(content != null);
+ var contentString = GetContent();
+ // Create stream of content even if contentString is empty, but remove content below
+ // except it is the only content stream.
+ content.CreateStream(PdfEncoders.RawEncoding.GetBytes(contentString));
_gfx = null!;
- _page.RenderContent!.SetRenderer(null);
- _page.RenderContent = null!;
+ content.SetRenderer(null);
+ _page.RenderContent = null;
+ // Remove empty content stream, but only if page has at least another content stream.
+ if (contentString.Length == 0 && _page.Contents.Elements.Count > 1)
+ {
+ _page.Contents.Elements.Remove(content);
+ Debug.Assert(_page.Contents.Elements.Count != 0);
+ }
+ content = null!;
_page = null!;
}
else if (_form != null!)
@@ -116,7 +147,7 @@ public void DrawLine(XPen pen, double x1, double y1, double x2, double y2)
public void DrawLines(XPen pen, XPoint[] points)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
if (pen == null)
@@ -130,7 +161,7 @@ public void DrawLines(XPen pen, XPoint[] points)
Realize(pen);
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y);
for (int idx = 1; idx < count; idx++)
AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y);
@@ -149,7 +180,7 @@ public void DrawBezier(XPen pen, double x1, double y1, double x2, double y2, dou
public void DrawBeziers(XPen pen, XPoint[] points)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
if (pen == null)
@@ -166,7 +197,7 @@ public void DrawBeziers(XPen pen, XPoint[] points)
Realize(pen);
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y);
for (int idx = 1; idx < count; idx += 3)
AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n",
@@ -182,7 +213,7 @@ public void DrawBeziers(XPen pen, XPoint[] points)
public void DrawCurve(XPen pen, XPoint[] points, double tension)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
if (pen == null)
@@ -201,7 +232,7 @@ public void DrawCurve(XPen pen, XPoint[] points, double tension)
Realize(pen);
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y);
if (count == 2)
{
@@ -223,7 +254,7 @@ public void DrawCurve(XPen pen, XPoint[] points, double tension)
public void DrawArc(XPen pen, double x, double y, double width, double height, double startAngle, double sweepAngle)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
if (pen == null)
@@ -240,7 +271,7 @@ public void DrawArc(XPen pen, double x, double y, double width, double height, d
public void DrawRectangle(XPen? pen, XBrush? brush, double x, double y, double width, double height)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
if (pen == null && brush == null)
@@ -248,7 +279,7 @@ public void DrawRectangle(XPen? pen, XBrush? brush, double x, double y, double w
throw new ArgumentNullException(nameof(pen) + " and " + nameof(brush));
}
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
Realize(pen, brush);
//AppendFormat123("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} re\n", x, y, width, -height);
@@ -279,7 +310,7 @@ public void DrawRectangles(XPen? pen, XBrush? brush, XRect[] rects)
public void DrawRoundedRectangle(XPen? pen, XBrush? brush, double x, double y, double width, double height, double ellipseWidth, double ellipseHeight)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
XGraphicsPath path = new XGraphicsPath();
@@ -292,7 +323,7 @@ public void DrawRoundedRectangle(XPen? pen, XBrush? brush, double x, double y, d
public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double width, double height)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
Realize(pen, brush);
@@ -309,7 +340,7 @@ public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double wid
double y0 = rect.Y + δy;
// Approximate an ellipse by drawing four cubic splines.
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", x0 + δx, y0);
AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n",
x0 + δx, y0 + fy, x0 + fx, y0 + δy, x0, y0 + δy);
@@ -327,7 +358,7 @@ public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double wid
public void DrawPolygon(XPen? pen, XBrush? brush, XPoint[] points, XFillMode fillmode)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
Realize(pen, brush);
@@ -336,7 +367,7 @@ public void DrawPolygon(XPen? pen, XBrush? brush, XPoint[] points, XFillMode fil
if (points.Length < 2)
throw new ArgumentException(PsMsgs.PointArrayAtLeast(2), nameof(points));
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y);
for (int idx = 1; idx < count; idx++)
AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y);
@@ -350,12 +381,12 @@ public void DrawPie(XPen? pen, XBrush? brush, double x, double y, double width,
double startAngle, double sweepAngle)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
Realize(pen, brush);
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", x + width / 2, y + height / 2);
AppendPartialArc(x, y, width, height, startAngle, sweepAngle, PathStart.LineTo1st, new XMatrix());
AppendStrokeFill(pen, brush, XFillMode.Alternate, true);
@@ -366,7 +397,7 @@ public void DrawPie(XPen? pen, XBrush? brush, double x, double y, double width,
public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, double tension, XFillMode fillmode)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
int count = points.Length;
@@ -380,7 +411,7 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, double te
Realize(pen, brush);
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y);
if (count == 2)
{
@@ -403,7 +434,7 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, double te
public void DrawPath(XPen? pen, XBrush? brush, XGraphicsPath path)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
if (pen == null && brush == null)
@@ -444,7 +475,7 @@ public void DrawPath(XPen? pen, XBrush? brush, XGraphicsPath path)
public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFormat format)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.DrawString });
font.CheckVersion();
@@ -652,7 +683,7 @@ public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFo
Debug.Assert(textParts.Sum(p => p.Count) == glyphCount, "Character count mismatch.");
- const string format2 = Config.SignificantDecimalPlaces4;
+ const string format2 = DefaultNumberFormat4;
var layerBytes = new byte[2];
foreach (var textPart in textParts)
{
@@ -670,7 +701,7 @@ public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFo
if (layer.paletteIndex != 0xffff)
{
var color = otDescriptor.FontFace.cpal!.colorRecords[layer.paletteIndex];
- _gfxState.RealizeBrush(new XSolidBrush(color), _colorMode, 0, 0);
+ _gfxState.RealizeBrush(new XSolidBrush(XColor.FromArgb(color.Argb)), _colorMode, 0, 0);
}
else
{
@@ -744,7 +775,7 @@ void RenderText(string text, XFont font, XBrush brush, double x, double y, doubl
}
// Select the number of decimal places used for the relative text positioning.
- const string formatTj = Config.SignificantDecimalPlaces4;
+ const string formatTj = DefaultNumberFormat4;
#if ITALIC_SIMULATION
if (italicSimulation)
{
@@ -805,8 +836,8 @@ void RenderText(string text, XFont font, XBrush brush, double x, double y, doubl
}
}
#else
- AdjustTextMatrix(ref pos);
- AppendFormat2("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text);
+ AdjustTextMatrix(ref pos);
+ AppendFormat2("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text);
#endif
if (underline)
{
@@ -880,10 +911,10 @@ public void DrawString(string s, XGlyphTypeface typeface, XBrush brush, XRect re
public void DrawImage(XImage image, double x, double y, double width, double height)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
string name = Realize(image);
if (image is not XForm form)
@@ -945,10 +976,10 @@ public void DrawImage(XImage image, double x, double y, double width, double hei
public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit srcUnit)
{
// #PDF-UA
- if (Owner._uaManager != null)
+ if (Owner.UAManager != null)
Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw });
- const string format = Config.SignificantDecimalPlaces4;
+ const string numFormat = DefaultNumberFormat4;
double x = destRect.X;
double y = destRect.Y;
@@ -960,13 +991,23 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit
{
if (_gfx.PageDirection == XPageDirection.Downwards)
{
- AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do\nQ\n",
+#if true
+ const string format = "q {2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n";
+ AppendFormatImage(format, x, y + height, width, height, name);
+#else
+ AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n",
x, y + height, width, height, name);
+#endif
}
else
{
+#if true
+ const string format = "q {2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n";
+ AppendFormatImage(format, x, y, width, height, name);
+#else
AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n",
x, y, width, height, name);
+#endif
}
}
else
@@ -984,7 +1025,13 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit
{
var xForm = form as XPdfForm;
// Reset colors in this graphics state. Usually PDF images should set them, but in rare cases they don’t and this may result in changed colors inside the image.
+#if true
+ bool resetColor = xForm != null;
+ const string resetColorCode = "\n0 g\n0 G\n";
+#else
+ bool resetColorXX = xForm != null;
var resetColor = xForm != null ? "\n0 g\n0 G\n" : " ";
+#endif
if (_gfx.PageDirection == XPageDirection.Downwards)
{
@@ -996,14 +1043,27 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit
xDraw -= xForm.Page!.MediaBox.X1;
yDraw += xForm.Page.MediaBox.Y1;
}
+#if true
+ const string formatReset = "q" + resetColorCode + "{2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n";
+ const string format = "q {2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n";
+
+ AppendFormatImage(resetColor ? formatReset : format, xDraw, yDraw + height, cx, cy, name);
+#else
AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n",
xDraw, yDraw + height, cx, cy, name);
+#endif
}
else
{
// TODO_OLD Translation for MediaBox.
+#if true
+ const string formatReset = "q" + resetColorCode + "{2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n";
+ const string format = "q {2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n";
+ AppendFormatImage(resetColor ? formatReset : format, x, y, cx, cy, name);
+#else
AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n",
x, y, cx, cy, name);
+#endif
}
}
}
@@ -1187,7 +1247,7 @@ void AppendPartialArc(double x, double y, double width, double height, double st
α = α + (1 + Math.Floor((Math.Abs(α) / 360))) * 360;
else if (α > 360)
α = α - Math.Floor(α / 360) * 360;
- Debug.Assert(α >= 0 && α <= 360);
+ Debug.Assert(α is >= 0 and <= 360);
double β = sweepAngle;
if (β < -360)
@@ -1335,7 +1395,7 @@ void AppendPartialArcQuadrant(double x, double y, double width, double height, d
sinβ = Math.Sin(β);
double cosβ = Math.Cos(β);
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
XPoint pt1, pt2, pt3;
if (!reflect)
{
@@ -1391,7 +1451,7 @@ void AppendPartialArcQuadrant(double x, double y, double width, double height, d
void AppendPartialArc(SysPoint point1, SysPoint point2, double rotationAngle,
SysSize size, bool isLargeArc, SweepDirection sweepDirection, PathStart pathStart)
{
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
Debug.Assert(pathStart == PathStart.Ignore1st);
@@ -1417,7 +1477,7 @@ void AppendPartialArc(SysPoint point1, SysPoint point2, double rotationAngle,
///
void AppendCurveSegment(XPoint pt0, XPoint pt1, XPoint pt2, XPoint pt3, double tension3)
{
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n",
pt1.X + tension3 * (pt2.X - pt0.X), pt1.Y + tension3 * (pt2.Y - pt0.Y),
pt2.X - tension3 * (pt3.X - pt1.X), pt2.Y - tension3 * (pt3.Y - pt1.Y),
@@ -1595,7 +1655,7 @@ internal void AppendPath(GraphicsPath path)
#if CORE || GDI
void AppendPath(XPoint[] points, byte[] types)
{
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
int count = points.Length;
if (count == 0)
return;
@@ -1649,7 +1709,7 @@ void AppendPath(XPoint[] points, byte[] types)
///
internal void AppendPath(PathGeometry geometry)
{
- const string format = Config.SignificantDecimalPlaces4;
+ const string format = DefaultNumberFormat4;
foreach (PathFigure figure in geometry.Figures)
{
@@ -1825,11 +1885,6 @@ internal void Append(string value)
internal void AppendFormatArgs(string format, params object[] args)
{
_content.AppendFormat(CultureInfo.InvariantCulture, format, args);
-#if DEBUG_
- string dummy = _content.ToString();
- dummy = dummy.Substring(Math.Max(0, dummy.Length - 100));
- dummy.GetType();
-#endif
}
internal void AppendFormatString(string format, string s)
@@ -1946,6 +2001,8 @@ internal void BeginPage()
DefaultViewMatrix = new XMatrix();
if (_gfx.PageDirection == XPageDirection.Downwards)
{
+ // Case Downwards, the default and only implemented direction.
+
// Take TrimBox into account.
PageHeightPt = Size.Height;
XPoint trimOffset = new XPoint();
@@ -1993,20 +2050,16 @@ internal void BeginPage()
if (!DefaultViewMatrix.IsIdentity)
{
Debug.Assert(_gfxState.RealizedCtm.IsIdentity);
- //_gfxState.RealizedCtm = DefaultViewMatrix;
- const string format = Config.SignificantDecimalPlaces7;
+ const string format = DefaultNumberFormat7;
double[] cm = DefaultViewMatrix.GetElements();
AppendFormatArgs("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ",
cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]);
}
-
- // Set page transformation
- //double[] cm = DefaultViewMatrix.GetElements();
- //AppendFormat("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ",
- // cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]);
}
else
{
+ // Case Upwards - not implemented.
+
// Scale with page units.
switch (_gfx.PageUnit)
{
@@ -2035,7 +2088,7 @@ internal void BeginPage()
// Save initial graphic state.
SaveState();
// Set page transformation.
- const string format = Config.SignificantDecimalPlaces7;
+ const string format = DefaultNumberFormat7;
double[] cm = DefaultViewMatrix.GetElements();
AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ",
cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]);
@@ -2075,13 +2128,8 @@ internal void BeginGraphicMode()
if (_streamMode == StreamMode.Graphic)
return;
- Debug.Assert(_streamMode == StreamMode.Text, "Undefined stream mode. Check what happened.");
-
- // Why the check?
- if (_streamMode == StreamMode.Text)
- _content.Append("ET\n");
-
_streamMode = StreamMode.Graphic;
+ _content.Append("ET\n");
}
///
@@ -2092,10 +2140,9 @@ internal void BeginTextMode()
if (_streamMode == StreamMode.Text)
return;
- Debug.Assert(_streamMode == StreamMode.Graphic, "Undefined stream mode. Check what happened.");
-
_streamMode = StreamMode.Text;
_content.Append("BT\n");
+
// Text matrix is empty after BT.
_gfxState.RealizedTextPosition = new XPoint();
_gfxState.ItalicSimulationOn = false;
@@ -2409,6 +2456,7 @@ PdfGraphicsState RestoreState(InternalGraphicsState state)
return top;
}
+ internal PdfGraphicsState PdfGraphicsState => _gfxState;
///
/// The current graphical state.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/GeometryHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/GeometryHelper.cs
index 61233900..b31b416b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/GeometryHelper.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/GeometryHelper.cs
@@ -410,7 +410,7 @@ public static List BezierCurveFromArc(XPoint point1, XPoint point2, XSiz
// Get vector from chord to center.
XVector vectRotated;
- // (comparing two Booleans here!)
+ // (comparing two booleans here!)
if (isLargeArc == isCounterclockwise)
vectRotated = new XVector(-vect.Y, vect.X);
else
@@ -430,7 +430,7 @@ public static List BezierCurveFromArc(XPoint point1, XPoint point2, XSiz
double α = Math.Atan2(pt1.Y - center.Y, pt1.X - center.X);
double β = Math.Atan2(pt2.Y - center.Y, pt2.X - center.X);
- // (another comparison of two Booleans!)
+ // (another comparison of two booleans!)
if (isLargeArc == (Math.Abs(β - α) < Math.PI))
{
if (α < β)
@@ -605,9 +605,9 @@ public static double
dist = (4/3)*(R - Rcos(a/2)) / Rsin(a/2)
and use some trig:
- __________
+ __________
cos(a/2) = \/1 + cos(a) / 2
- ________________ __________
+ ________________ __________
R*cos(a/2) = \/R^2 + R^2 cos(a) / 2 = \/R^2 + rDot / 2 */
double cos = (radSquared + dot) / 2; // =(R*cos(a))^2
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs
index b200bab5..38eec1b1 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs
@@ -3,6 +3,7 @@
using PdfSharp.Internal;
#if GDI
+using PdfSharp.Internal.Threading;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
@@ -68,10 +69,10 @@ public override void Save(Stream stream)
}
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
Source._gdiImage.Save(stream, ImageFormat.Png);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
DiagnosticsHelper.ThrowNotImplementedException("Save...");
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs
index ebd7a0ba..1335e54f 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs
@@ -1,15 +1,15 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-#if GDI
+
using PdfSharp.Internal;
+using PdfSharp.Internal.Threading;
+#if GDI
#endif
#if WPF
-using PdfSharp.Internal;
#endif
#if WUI
using Windows.UI.Xaml.Media.Imaging;
-using PdfSharp.Internal;
#endif
namespace PdfSharp.Drawing
@@ -29,11 +29,11 @@ internal XBitmapImage(int width, int height)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
// Create a default 24-bit ARGB bitmap.
_gdiImage = new Bitmap(width, height);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
DiagnosticsHelper.ThrowNotImplementedException("CreateBitmap");
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs
index 78eb9bc9..724cf93b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs
@@ -1,8 +1,7 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-#if CORE
-#endif
+using PdfSharp.Internal.Threading;
#if GDI
using PdfSharp.Internal;
#endif
@@ -29,17 +28,18 @@ public override int PixelWidth
get
{
#if CORE
- if (_importedImage != null)
- return (int)_importedImage.Information.Width;
+ if (_importedImage!= null)
+ return _importedImage.PixelWidth;
+
return 100;
#endif
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.Width;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
int gdiWidth = _gdiImage.Width;
@@ -65,16 +65,17 @@ public override int PixelHeight
{
#if CORE
if (_importedImage != null)
- return (int)_importedImage.Information.Height;
+ return _importedImage.PixelHeight;
+
return 100;
#endif
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.Height;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
int gdiHeight = _gdiImage.Height;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBrush.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBrush.cs
index 2304ed2e..754eea27 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBrush.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBrush.cs
@@ -37,11 +37,11 @@ public static implicit operator XBrush(Brush brush)
else if (brush is LinearGradientBrush lgBrush)
{
// xbrush = new LinearGradientBrush(lgBrush.Rectangle, lgBrush.co(solidBrush.Color);
- throw new NotImplementedException("XBrush type not yet supported by PDFsharp.");
+ throw new NotSupportedException("XBrush type not yet supported by PDFsharp.");
}
else
{
- throw new NotImplementedException("XBrush type not supported by PDFsharp.");
+ throw new NotSupportedException("XBrush type not supported by PDFsharp.");
}
return xbrush;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs
index 79886098..8ea51b0c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs
@@ -1,7 +1,8 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using System.ComponentModel;
+using PdfSharp.Internal;
#if GDI
using System.Drawing;
#endif
@@ -125,18 +126,15 @@ internal XColor(XKnownColor knownColor)
///
/// Creates an XColor structure from a 32-bit ARGB value.
///
- public static XColor FromArgb(int argb)
- {
- return new XColor((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb));
- }
+ [Obsolete("Use FromArgb(uint argb).")]
+ public static XColor FromArgb(int argb)
+ => new((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb));
///
/// Creates an XColor structure from a 32-bit ARGB value.
///
- public static XColor FromArgb(uint argb)
- {
- return new XColor((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb));
- }
+ public static XColor FromArgb(uint argb)
+ => new((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb));
// from System.Drawing.Color
//public static XColor FromArgb(int alpha, Color baseColor);
@@ -291,8 +289,8 @@ public static XColor FromName(string name)
{
#if GDI
// The implementation in System.Drawing.dll is interesting. It uses a ColorConverter
- // with hash tables, locking mechanisms etc. Im not sure what problems that solves.
- // So I dont use the source, but the reflection.
+ // with hash tables, locking mechanisms etc. I’m not sure what problems that solves.
+ // So I don’t use the source, but the reflection.
try
{
return new XColor((KnownColor)Enum.Parse(typeof(KnownColor), name, true));
@@ -732,27 +730,6 @@ public double GS
///
public static readonly XColor Empty = new();
- ///
- /// Special property for XmlSerializer only.
- ///
- public string RgbCmykG
- {
- get => Invariant($"{_r};{_g};{_b};{_c:0.###};{_m:0.###};{_y:0.###};{_k:0.###};{_gs:0.###};{_a:0.###}");
- set
- {
- string[] values = value.Split(';');
- _r = byte.Parse(values[0], CultureInfo.InvariantCulture);
- _g = byte.Parse(values[1], CultureInfo.InvariantCulture);
- _b = byte.Parse(values[2], CultureInfo.InvariantCulture);
- _c = float.Parse(values[3], CultureInfo.InvariantCulture);
- _m = float.Parse(values[4], CultureInfo.InvariantCulture);
- _y = float.Parse(values[5], CultureInfo.InvariantCulture);
- _k = float.Parse(values[6], CultureInfo.InvariantCulture);
- _gs = float.Parse(values[7], CultureInfo.InvariantCulture);
- _a = float.Parse(values[8], CultureInfo.InvariantCulture);
- }
- }
-
static void CheckByte(int val, string name)
{
if (val is < 0 or > 0xFF)
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs
index 329474fa..438b1821 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs
@@ -2,6 +2,13 @@
// See the LICENSE file in the solution root for more information.
using System.ComponentModel;
+using PdfSharp.Internal.OpenType;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Fonts;
+using PdfSharp.Fonts.Internal;
+using PdfSharp.Internal;
+using PdfSharp.Pdf;
+using PdfSharp.Pdf.Advanced;
#if GDI
using GdiFontFamily = System.Drawing.FontFamily;
using GdiFont = System.Drawing.Font;
@@ -16,14 +23,10 @@
#if WUI
using UwpFontFamily = Windows.UI.Xaml.Media.FontFamily;
#endif
-using PdfSharp.Fonts;
-using PdfSharp.Fonts.Internal;
-using PdfSharp.Fonts.OpenType;
-using PdfSharp.Internal;
-using PdfSharp.Pdf;
-using PdfSharp.Pdf.Advanced;
-// ReSharper disable ConvertToAutoProperty
+// v7.0 review
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Drawing
{
@@ -345,7 +348,7 @@ void Initialize()
// In principle an XFont is an XGlyphTypeface plus an em-size.
GlyphTypeface = XGlyphTypeface.GetOrCreateFrom(_familyName, fontResolvingOptions);
- GlyphTypeface.FontFace.SetFontEmbedding(_pdfOptions.FontEmbedding);
+ GlyphTypeface.OTFontFace.SetFontEmbedding((OpenTypeFontEmbedding)_pdfOptions.FontEmbedding);
}
#if GDI
// Create font by using font family.
@@ -359,7 +362,7 @@ void Initialize()
WpfFontFamily = GlyphTypeface.FontFamily.WpfFamily;
WpfTypeface = GlyphTypeface.WpfTypeface;
- WpfFontFamily ??= new WpfFontFamily(Name2);
+ WpfFontFamily ??= new WpfFontFamily(Name);
WpfTypeface ??= FontHelper.CreateTypeface(WpfFontFamily ?? NRT.ThrowOnNull(), _style);
#endif
@@ -374,7 +377,7 @@ void InitializeFromGdi()
{
try
{
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
if (GdiFontFamily != null!)
{
// Create font based on its family.
@@ -383,13 +386,8 @@ void InitializeFromGdi()
if (GdiFont != null!)
{
-#if DEBUG_
- string name1 = _gdiFont.Name;
- string name2 = _gdiFont.OriginalFontName;
- string name3 = _gdiFont.SystemFontName;
-#endif
_familyName = GdiFont.FontFamily.Name;
- // TODO_OLD: _glyphTypeface = XGlyphTypeface.GetOrCreateFrom(_gdiFont);
+ // TODO_OLD: _glyphTypeface = XGlyphTypeface.GetOrCreateFromBytes(_gdiFont);
}
else
{
@@ -401,7 +399,7 @@ void InitializeFromGdi()
CreateDescriptorAndInitializeFontMetrics();
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
#endif
@@ -438,10 +436,18 @@ void InitializeFromWpf()
void CreateDescriptorAndInitializeFontMetrics() // TODO_OLD: refactor
{
Debug.Assert(_fontMetrics == null, "InitializeFontMetrics() was already called.");
- OpenTypeDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(this); //_familyName, _style, _glyphTypeface.FontFace);
- _fontMetrics = new XFontMetrics(OpenTypeDescriptor.FontName3, OpenTypeDescriptor.UnitsPerEm, OpenTypeDescriptor.Ascender, OpenTypeDescriptor.Descender,
- OpenTypeDescriptor.Leading, OpenTypeDescriptor.LineSpacing, OpenTypeDescriptor.CapHeight, OpenTypeDescriptor.XHeight, OpenTypeDescriptor.StemV, 0, 0, 0,
- OpenTypeDescriptor.UnderlinePosition, OpenTypeDescriptor.UnderlineThickness, OpenTypeDescriptor.StrikeoutPosition, OpenTypeDescriptor.StrikeoutSize);
+
+ //var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache;
+ //OpenTypeDescriptor = (OpenTypeDescriptor)fontDescriptorCache.GetOrCreateDescriptorFor(this); //_familyName, _style, _glyphTypeface.FontFace);
+ //Debug.Assert(ReferenceEquals(OpenTypeDescriptor, GlyphTypeface.OTFontFace.OTDescriptor));
+ OpenTypeDescriptor = GlyphTypeface.OTFontFace.OTDescriptor;
+
+ _fontMetrics = new XFontMetrics(OpenTypeDescriptor.Key, OpenTypeDescriptor.UnitsPerEm,
+ OpenTypeDescriptor.Ascender, OpenTypeDescriptor.Descender, OpenTypeDescriptor.Leading,
+ OpenTypeDescriptor.LineSpacing, OpenTypeDescriptor.CapHeight, OpenTypeDescriptor.XHeight,
+ OpenTypeDescriptor.StemV, 0, 0, 0,
+ OpenTypeDescriptor.UnderlinePosition, OpenTypeDescriptor.UnderlineThickness,
+ OpenTypeDescriptor.StrikeoutPosition, OpenTypeDescriptor.StrikeoutSize);
XFontMetrics fm = Metrics;
@@ -479,13 +485,15 @@ void CreateDescriptorAndInitializeFontMetrics() // TODO_OLD: refactor
[Browsable(false)]
public XFontFamily FontFamily => GlyphTypeface.FontFamily;
+ [Obsolete("Use Name to get the fonts family name. Name2 was accidentally released.")]
+ public string Name2 => GlyphTypeface.FontFamily.Name;
+
///
/// Gets the font family name.
///
- // [Obsolete("This function returns the font family name, not the face name. Use xxx.FontFamily.Name or xxx.FaceName")]
- public string Name2 => GlyphTypeface.FontFamily.Name;
+ public string Name => GlyphTypeface.FamilyName;
- internal string FaceName => GlyphTypeface.FaceName;
+ //internal string FaceName => GlyphTypeface.FontName;
///
/// Gets the em-size of this font measured in the unit of this font object.
@@ -567,34 +575,19 @@ public int CellSpace
int _cellSpace;
///
- /// Gets the cell ascent, the area above the base line that is used by the font.
+ /// Gets the cell ascent, the area above the baseline that is used by the font.
///
public int CellAscent { get; internal set; }
///
- /// Gets the cell descent, the area below the base line that is used by the font.
+ /// Gets the cell descent, the area below the baseline that is used by the font.
///
public int CellDescent { get; internal set; }
///
/// Gets the font metrics.
///
- /// The metrics.
- public XFontMetrics Metrics
- {
- get
- {
- // Code moved to InitializeFontMetrics().
- //if (_fontMetrics == null)
- //{
- // FontDescriptor descriptor = FontDescriptorStock.Global.CreateDescriptor(this);
- // _fontMetrics = new XFontMetrics(descriptor.FontName, descriptor.UnitsPerEm, descriptor.Ascender, descriptor.Descender,
- // descriptor.Leading, descriptor.LineSpacing, descriptor.CapHeight, descriptor.XHeight, descriptor.StemV, 0, 0, 0);
- //}
- Debug.Assert(_fontMetrics != null, "InitializeFontMetrics() not yet called.");
- return _fontMetrics;
- }
- }
+ public XFontMetrics Metrics => _fontMetrics;
XFontMetrics _fontMetrics = default!;
@@ -690,11 +683,11 @@ public double GetHeight(XGraphics graphics)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- internal XGlyphTypeface GlyphTypeface { get; private set; } = default!;
+ internal XGlyphTypeface GlyphTypeface { get; private set; } = null!;
- internal OpenTypeDescriptor OpenTypeDescriptor { get; private set; } = default!;
+ internal OpenTypeFontDescriptor OpenTypeDescriptor { get; private set; } = null!;
- internal string FamilyName => _familyName;
+ string FamilyName => _familyName;
string _familyName = "";
@@ -748,11 +741,11 @@ internal static XFontStyleEx FontStyleFrom(GdiFont font)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ///
- /// Cache PdfFontTable.FontSelector to speed up finding the right PdfFont
- /// if this font is used more than once.
- ///
- internal string? PdfFontSelector { get; set; }
+ /////
+ ///// Cache PdfFontTable.FontSelector to speed up finding the right PdfFont
+ ///// if this font is used more than once.
+ /////
+ //internal string? PdfFontSelector { get; set; }
internal void CheckVersion() => GlyphTypeface.CheckVersion();
@@ -763,7 +756,7 @@ internal static XFontStyleEx FontStyleFrom(GdiFont font)
string DebuggerDisplay
// ReSharper restore UnusedMember.Local
{
- get => Invariant($"font=('{Name2}' {Size:0.##}{(Bold ? " bold" : "")}{(Italic ? " italic" : "")} {GlyphTypeface.StyleSimulations})");
+ get => Invariant($"font=('{Name}' {Size:0.##}{(Bold ? " bold" : "")}{(Italic ? " italic" : "")} {GlyphTypeface.StyleSimulations})");
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontFamily.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontFamily.cs
index 5ac663b8..a55ba173 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontFamily.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontFamily.cs
@@ -1,18 +1,19 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal.OpenType;
+using PdfSharp.Fonts;
+using PdfSharp.Internal;
#if GDI
using GdiFont = System.Drawing.Font;
using GdiFontFamily = System.Drawing.FontFamily;
using GdiFontStyle = System.Drawing.FontStyle;
#endif
#if WPF
+using PdfSharp.Fonts.Internal;
using WpfFontFamily = System.Windows.Media.FontFamily;
using WpfFontStyle = System.Windows.FontStyle;
#endif
-using PdfSharp.Fonts;
-using PdfSharp.Fonts.Internal;
-using PdfSharp.Fonts.OpenType;
namespace PdfSharp.Drawing
{
@@ -62,20 +63,20 @@ internal XFontFamily(string familyName, bool createPlatformObjects)
//}
#endif
- internal static XFontFamily CreateFromName_not_used(string name, bool createPlatformFamily)
- {
- XFontFamily fontFamily = new XFontFamily(name);
- if (createPlatformFamily)
- {
-#if GDI
- //fontFamily._gdiFamily = new System.Drawing.FontFamily(name);
-#endif
-#if WPF
- //fontFamily._wpfFamily = new System.Windows.Media.FontFamily(name);
-#endif
- }
- return fontFamily;
- }
+//// internal static XFontFamily CreateFromName_not_used(string name, bool createPlatformFamily)
+//// {
+//// XFontFamily fontFamily = new XFontFamily(name);
+//// if (createPlatformFamily)
+//// {
+////#if GDI
+//// //fontFamily._gdiFamily = new System.Drawing.FontFamily(name);
+////#endif
+////#if WPF
+//// //fontFamily._wpfFamily = new System.Windows.Media.FontFamily(name);
+////#endif
+//// }
+//// return fontFamily;
+//// }
///
/// An XGlyphTypeface for a font source that comes from a custom font resolver
@@ -84,15 +85,16 @@ internal static XFontFamily CreateFromName_not_used(string name, bool createPlat
internal static XFontFamily GetOrCreateFontFamily(string name)
{
// Custom font resolver face names must not clash with platform family names.
- var fontFamilyInternal = FontFamilyCache.GetFamilyByName(name);
+ var fontFamilyCache = PsGlobals.Global.Fonts.FontFamilyCache;
+ var fontFamilyInternal = fontFamilyCache.GetFamilyByName(name);
if (fontFamilyInternal == null)
{
fontFamilyInternal = FontFamilyInternal.GetOrCreateFromName(name, false);
- fontFamilyInternal = FontFamilyCache.CacheOrGetFontFamily(fontFamilyInternal);
+ fontFamilyInternal = fontFamilyCache.CacheOrGetFontFamily(fontFamilyInternal);
}
// Create font family and save it in cache. Do not try to create platform objects.
- return new XFontFamily(fontFamilyInternal);
+ return new(fontFamilyInternal);
}
#if GDI
@@ -107,7 +109,7 @@ internal static XFontFamily GetOrCreateFromGdi(GdiFont font)
internal static XFontFamily GetOrCreateFromWpf(WpfFontFamily wpfFontFamily)
{
FontFamilyInternal fontFamilyInternal = FontFamilyInternal.GetOrCreateFromWpf(wpfFontFamily);
- return new XFontFamily(fontFamilyInternal);
+ return new(fontFamilyInternal);
}
#endif
@@ -132,7 +134,9 @@ public double LineSpacing
///
public int GetCellAscent(XFontStyleEx style)
{
- OpenTypeDescriptor descriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptor(Name, style);
+ var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache;
+ OpenTypeFontDescriptor descriptor = (OpenTypeFontDescriptor)fontDescriptorCache.GetOrCreateDescriptor(Name, style);
+
int result = descriptor.Ascender;
#if DEBUG_ && GDI
int gdiValue = _gdiFamily.GetCellAscent((FontStyle)style);
@@ -146,7 +150,8 @@ public int GetCellAscent(XFontStyleEx style)
///
public int GetCellDescent(XFontStyleEx style)
{
- OpenTypeDescriptor descriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptor(Name, style);
+ var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache;
+ OpenTypeFontDescriptor descriptor = (OpenTypeFontDescriptor)fontDescriptorCache.GetOrCreateDescriptor(Name, style);
int result = descriptor.Descender;
#if DEBUG_ && GDI
int gdiValue = _gdiFamily.GetCellDescent((FontStyle)style);
@@ -160,7 +165,8 @@ public int GetCellDescent(XFontStyleEx style)
///
public int GetEmHeight(XFontStyleEx style)
{
- OpenTypeDescriptor descriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptor(Name, style);
+ var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache;
+ OpenTypeFontDescriptor descriptor = (OpenTypeFontDescriptor)fontDescriptorCache.GetOrCreateDescriptor(Name, style);
int result = descriptor.UnitsPerEm;
#if DEBUG_ && GDI
int gdiValue = _gdiFamily.GetEmHeight((FontStyle)style);
@@ -179,7 +185,8 @@ public int GetEmHeight(XFontStyleEx style)
///
public int GetLineSpacing(XFontStyleEx style)
{
- OpenTypeDescriptor descriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptor(Name, style);
+ var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache;
+ OpenTypeFontDescriptor descriptor = (OpenTypeFontDescriptor)fontDescriptorCache.GetOrCreateDescriptor(Name, style);
int result = descriptor.LineSpacing;
#if DEBUG_ && GDI
int gdiValue = _gdiFamily.GetLineSpacing((FontStyle)style);
@@ -204,12 +211,12 @@ public bool IsStyleAvailable(XFontStyleEx style)
throw new InvalidOperationException("In Core build it is the responsibility of the developer to provide all required font faces.");
#endif
#if GDI && !WPF
- if (GdiFamily != null)
+ if (GdiFamily != null!)
return GdiFamily.IsStyleAvailable((GdiFontStyle)xStyle);
return false;
#endif
#if WPF && !GDI
- if (WpfFamily != null)
+ if (WpfFamily != null!)
return FontHelper.IsStyleAvailable(this, xStyle);
return false;
#endif
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontSource.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontSource.cs
index 47aaa679..01cb0d18 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontSource.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontSource.cs
@@ -2,10 +2,11 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Fonts;
-using PdfSharp.Fonts.Internal;
+using PdfSharp.Internal;
+using PdfSharp.Internal.OpenType;
+
#if GDI
using System.Runtime.InteropServices;
-using PdfSharp.Internal;
using GdiFont = System.Drawing.Font;
using GdiFontStyle = System.Drawing.FontStyle;
#endif
@@ -14,7 +15,8 @@
using WpfTypeface = System.Windows.Media.Typeface;
using WpfGlyphTypeface = System.Windows.Media.GlyphTypeface;
#endif
-using PdfSharp.Fonts.OpenType;
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Drawing
{
@@ -24,21 +26,23 @@ namespace PdfSharp.Drawing
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
public class XFontSource
{
- // Implementation Notes
- //
- // * XFontSource represents a single font (file) in memory.
- // * An XFontSource holds a reference to its OpenTypeFontFace.
- // * To prevent large heap fragmentation this class must exist only once.
- // * ttcf postponed to PDFsharp.Fonts.
+ // Based on OpenTypeFontSource.
+
+ // Implementation moved to OpenTypeFontSource.
+ // XFontSource is just a thin wrapper of this class.
// Signature of a true type collection font.
const uint ttcf = 0x66637474;
- XFontSource(byte[] bytes, ulong key)
+
+ XFontSource(byte[] bytes, ulong checksumKey = 0)
{
- //_fontName = null!; // B_UG?
- Bytes = bytes;
- _key = key;
+ _otFontSource = OpenTypeFontSource.GetOrCreateFrom(bytes, checksumKey);
+ }
+
+ XFontSource(OpenTypeFontSource otFontSource)
+ {
+ _otFontSource = otFontSource;
}
///
@@ -47,13 +51,24 @@ public class XFontSource
///
public static XFontSource GetOrCreateFrom(byte[] bytes)
{
- ulong key = FontHelper.CalcChecksum(bytes);
- if (!FontFactory.TryGetFontSourceByKey(key, out var fontSource))
- {
- fontSource = new XFontSource(bytes, key);
- // Theoretically the font source could be created by a different thread in the meantime.
- fontSource = FontFactory.CacheFontSource(fontSource);
- }
+ ulong checksumKey = ChecksumHelper.CalcChecksum(bytes);
+ var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache;
+ if (fontSourceCache.TryGetFontSourceByKey(checksumKey, out var fontSource))
+ return fontSource;
+
+ fontSource = new(bytes, checksumKey);
+ fontSource = fontSourceCache.CacheFontSource(fontSource);
+ return fontSource;
+ }
+
+ public static XFontSource GetOrCreateFrom(OpenTypeFontSource otFontSource)
+ {
+ var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache;
+ if (fontSourceCache.TryGetFontSourceByKey(otFontSource.ChecksumKey, out var fontSource))
+ return fontSource;
+
+ fontSource = new(otFontSource);
+ fontSource = fontSourceCache.CacheFontSource(fontSource);
return fontSource;
}
@@ -70,17 +85,16 @@ public static XFontSource CreateFromFile(string path)
return GetOrCreateFrom(bytes);
}
-#if CORE
- internal static XFontSource GetOrCreateFromGlyphTypeface(string typefaceKey, XGlyphTypeface? glyphTypeface)
+ ///
+ /// Creates an XFontSource from a uri to a font file.
+ ///
+ public static XFontSource CreateFromFile(Uri fontSourceUri)
{
- // #CORE NYI
- throw new NotImplementedException(nameof(GetOrCreateFromGlyphTypeface));
+ if (!fontSourceUri.IsFile)
+ throw new ArgumentException(GfxMsgs.Font_NoFileUri(fontSourceUri).Message);
- //byte[] bytes = null; //FontDataHelper.SegoeWP;
- //XFontSource fontSource = GetOrCreateFrom(typefaceKey, bytes);
- //return fontSource;
+ return CreateFromFile(fontSourceUri.LocalPath);
}
-#endif
#if GDI
internal static XFontSource? GetOrCreateFromGdi(string typefaceKey, GdiFont gdiFont)
@@ -128,7 +142,7 @@ static byte[] ReadFontBytesFromGdi(GdiFont gdiFont)
isTtcf = true;
}
error = Marshal.GetLastWin32Error();
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
Debug.Assert(error == 0);
#else
// We ignore error 127 here.
@@ -178,8 +192,9 @@ internal static byte[] ReadFontBytesFromWpf(WpfGlyphTypeface wpfGlyphTypeface)
static XFontSource GetOrCreateFrom(string typefaceKey, byte[] fontBytes)
{
- ulong key = FontHelper.CalcChecksum(fontBytes);
- if (FontFactory.TryGetFontSourceByKey(key, out var fontSource))
+ ulong checksumKey = ChecksumHelper.CalcChecksum(fontBytes);
+ var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache;
+ if (fontSourceCache.TryGetFontSourceByKey(checksumKey, out var fontSource))
{
// The font source already exists, but is not yet cached under the specified typeface key.
FontFactory.CacheExistingFontSourceWithNewTypefaceKey(typefaceKey, fontSource);
@@ -187,8 +202,8 @@ static XFontSource GetOrCreateFrom(string typefaceKey, byte[] fontBytes)
else
{
// No font source exists. Create new one and cache it.
- fontSource = new XFontSource(fontBytes, key);
- FontFactory.CacheNewFontSource(typefaceKey, fontSource);
+ fontSource = new(fontBytes, checksumKey);
+ fontSourceCache.CacheNewFontSource(typefaceKey, fontSource);
}
return fontSource;
}
@@ -205,48 +220,37 @@ public static XFontSource CreateCompiledFont(byte[] bytes)
///
/// Gets or sets the font face.
///
- internal OpenTypeFontFace FontFace
+ internal OpenTypeFontFace OTFontFace
{
- get => _fontFace;
- set
- {
- _fontFace = value;
- _fontName = value.name.FullFontName;
- }
+ get => _otFontSource.OTFontFace;
+ set => _otFontSource.OTFontFace = value;
}
- OpenTypeFontFace _fontFace = default!; // NRT
///
/// Gets the key that uniquely identifies this font source.
///
- internal ulong Key
+ internal ulong ChecksumKey
{
- get
- {
- if (_key == 0)
- _key = FontHelper.CalcChecksum(Bytes);
- return _key;
- }
+ get => _otFontSource.ChecksumKey;
}
- ulong _key;
///
- /// Gets the name of the font’s name table.
+ /// //??? What??? Gets the name of the font’s name table.
///
- public string FontName => _fontName;
+ public string FontFaceKey => _otFontSource.OTFontFace.OTDescriptor.Key ?? null!;
- string _fontName = default!;
+ //string _fontFaceKey = null!;
///
/// Gets the bytes of the font.
///
- public byte[] Bytes { get; }
+ public byte[] Bytes => _otFontSource.Bytes;
///
/// Returns a hash code for this instance.
///
public override int GetHashCode()
- => (int)((Key >> 32) ^ Key);
+ => _otFontSource.GetHashCode();
///
/// Determines whether the specified object is equal to the current object.
@@ -259,14 +263,18 @@ public override bool Equals(object? obj)
{
if (obj is not XFontSource fontSource)
return false;
- return Key == fontSource.Key;
+ return ChecksumKey == fontSource.ChecksumKey;
}
+ public OpenTypeFontSource OTFontSource => _otFontSource;
+
+ readonly OpenTypeFontSource _otFontSource;
+
///
/// Gets the DebuggerDisplayAttribute text.
///
// ReSharper disable UnusedMember.Local
internal string DebuggerDisplay =>
- String.Format(CultureInfo.InvariantCulture, "XFontSource: '{0}', keyhash={1}", FontName, Key % 99991 /* largest prime number less than 100000 */);
+ String.Format(CultureInfo.InvariantCulture, "XFontSource: '{0}', keyhash={1}", FontFaceKey, ChecksumKey % 99991 /* largest prime number less than 100000 */);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XForm.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XForm.cs
index 1fb078bd..79c8a22f 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XForm.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XForm.cs
@@ -9,6 +9,8 @@
using PdfSharp.Drawing.Pdf;
using PdfSharp.Pdf;
using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.IO;
+using static PdfSharp.Pdf.PdfDictionary;
namespace PdfSharp.Drawing
{
@@ -59,7 +61,7 @@ public XForm(XGraphics gfx, XSize size)
if (gfx == null)
throw new ArgumentNullException(nameof(gfx));
if (size.Width < 1 || size.Height < 1)
- throw new ArgumentNullException(nameof(size), "The size of the XPdfForm is to small.");
+ throw new ArgumentNullException(nameof(size), "The size of the XPdfForm is too small.");
_formState = FormState.Created;
//templateSize = size;
@@ -98,7 +100,7 @@ public XForm(XGraphics gfx, XUnit width, XUnit height)
public XForm(PdfDocument document, XRect viewBox)
{
if (viewBox.Width < 1 || viewBox.Height < 1)
- throw new ArgumentNullException(nameof(viewBox), "The size of the XPdfForm is to small.");
+ throw new ArgumentNullException(nameof(viewBox), "The size of the XPdfForm is too small.");
// I must tie the XPdfForm to a document immediately, because otherwise I would have no place where
// to store the resources.
if (document == null!)
@@ -113,6 +115,33 @@ public XForm(PdfDocument document, XRect viewBox)
_pdfForm.Elements.SetRectangle(PdfFormXObject.Keys.BBox, rect);
}
+ ///
+ /// Initializes a new instance of the class that represents an existing PdfFormXObject.
+ ///
+ /// The PdfFormXObject.
+ /// The bounding box of the PdfFormXObject.
+ public XForm(PdfFormXObject formXObject, PdfRectangle boundingBox)
+ {
+ var viewBox = boundingBox.ToXRect();
+ var document = formXObject.Document;
+
+ if (viewBox.Width < 1 || viewBox.Height < 1)
+ throw new ArgumentNullException(nameof(viewBox), "The size of the XPdfForm is too small.");
+ // I must tie the XPdfForm to a document immediately, because otherwise I would have no place where
+ // to store the resources.
+ if (document == null!)
+ throw new ArgumentNullException(nameof(document), "An XPdfForm template must be associated with a document at creation time.");
+
+ _formState = FormState.Created;
+ _document = document;
+ _pdfForm = formXObject;
+ formXObject.SetForm(this);
+ //_templateSize = size;
+ _viewBox = viewBox;
+
+ _pdfForm.Elements.SetRectangle(PdfFormXObject.Keys.BBox, boundingBox);
+ }
+
///
/// Initializes a new instance of the class that represents a page of a PDF document.
///
@@ -122,7 +151,7 @@ public XForm(PdfDocument document, XSize size)
: this(document, new XRect(0, 0, size.Width, size.Height))
{
////if (size.width < 1 || size.height < 1)
- //// throw new ArgumentNullException("size", "The size of the XPdfForm is to small.");
+ //// throw new ArgumentNullException("size", "The size of the XPdfForm is too small.");
////// I must tie the XPdfForm to a document immediately, because otherwise I would have no place where
////// to store the resources.
////if (document == null)
@@ -170,7 +199,7 @@ public void DrawingFinished()
internal void AssociateGraphics(XGraphics gfx)
{
if (_formState == FormState.NotATemplate)
- throw new NotImplementedException("The current version of PDFsharp cannot draw on an imported page.");
+ throw new NotSupportedException("The current version of PDFsharp cannot draw on an imported page.");
if (_formState == FormState.UnderConstruction)
throw new InvalidOperationException("An XGraphics object already exists for this form.");
@@ -182,7 +211,7 @@ internal void AssociateGraphics(XGraphics gfx)
_formState = FormState.UnderConstruction;
Gfx = gfx;
}
- internal XGraphics Gfx = default!;
+ internal XGraphics? Gfx;
#if true_
///
@@ -199,17 +228,43 @@ protected override void Dispose(bool disposing)
///
internal virtual void Finish()
{
+#if CORE
+ if (_formState is FormState.NotATemplate or FormState.Finished)
+ return;
+
+
+ Debug.Assert(_formState is FormState.Created or FormState.UnderConstruction);
+ _formState = FormState.Finished;
+ Gfx?.Dispose();
+ Gfx = null;
+
+ if (PdfRenderer != null!)
+ {
+ //pdfForm.CreateStream(PdfEncoders.RawEncoding.GetBytes(PdfRenderer.GetContent()));
+ PdfRenderer.Close();
+ Debug.Assert(PdfRenderer == null!);
+#if true_
+ if (_document.Options.CompressContentStreams)
+ {
+ _pdfForm!.Stream!.Value = Filtering.FlateDecode.Encode(_pdfForm.Stream.Value, _document.Options.FlateEncodeMode);
+ _pdfForm.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
+ }
+#endif
+ int length = _pdfForm!.Stream!.Length;
+ _pdfForm.Elements.SetInteger("/Length", length);
+ }
+#endif
#if GDI
if (_formState is FormState.NotATemplate or FormState.Finished)
return;
- if (Gfx.Metafile != null)
+ if (Gfx?.Metafile != null)
_gdiImage = Gfx.Metafile;
Debug.Assert(_formState is FormState.Created or FormState.UnderConstruction);
_formState = FormState.Finished;
- Gfx.Dispose();
- Gfx = null!;
+ Gfx?.Dispose();
+ Gfx = null;
if (PdfRenderer != null!)
{
@@ -219,11 +274,11 @@ internal virtual void Finish()
if (_document.Options.CompressContentStreams)
{
- _pdfForm!.Stream.Value = Filtering.FlateDecode.Encode(_pdfForm.Stream.Value, _document.Options.FlateEncodeMode);
- _pdfForm.Elements["/Filter"] = new PdfName("/FlateDecode");
+ _pdfForm!.Stream!.Value = Filtering.FlateDecode.Encode(_pdfForm.Stream.Value, _document.Options.FlateEncodeMode);
+ _pdfForm.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
}
- int length = _pdfForm!.Stream.Length;
- _pdfForm.Elements.SetInteger("/Length", length);
+ int length = _pdfForm!.Stream!.Length;
+ _pdfForm.Elements.SetInteger(PdfStream.Keys.Length, length);
}
#endif
#if WPF
@@ -336,7 +391,7 @@ public virtual XMatrix Transform
_transform = value;
}
}
- internal XMatrix _transform;
+ XMatrix _transform;
internal PdfResources Resources
{
@@ -368,7 +423,7 @@ internal string GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out
return name;
}
- string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont)
+ string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont)
=> GetFontName(glyphTypeface, fontType, out pdfFont);
///
@@ -399,7 +454,7 @@ internal string GetFontName(string idName, byte[] fontData, out PdfFont pdfFont)
return name;
}
- string IContentStream.GetFontName(string idName, byte[] fontData, out PdfFont pdfFont)
+ string IContentStream.GetFontName(string idName, byte[] fontData, out PdfFont pdfFont)
=> GetFontName(idName, fontData, out pdfFont);
///
@@ -417,7 +472,7 @@ internal string GetImageName(XImage image)
///
/// Implements the interface because the primary function is internal.
///
- string IContentStream.GetImageName(XImage image)
+ string IContentStream.GetImageName(XImage image)
=> GetImageName(image);
internal PdfFormXObject PdfForm
@@ -446,7 +501,7 @@ internal string GetFormName(XForm form)
///
/// Implements the interface because the primary function is internal.
///
- string IContentStream.GetFormName(XForm form)
+ string IContentStream.GetFormName(XForm form)
=> GetFormName(form);
///
@@ -455,7 +510,7 @@ string IContentStream.GetFormName(XForm form)
///
internal PdfFormXObject? _pdfForm; // TODO_OLD: make private
- internal XGraphicsPdfRenderer PdfRenderer = default!;
+ internal XGraphicsPdfRenderer PdfRenderer = null!;
#if WPF
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs
index 45a7df93..85a06eaa 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs
@@ -1,6 +1,13 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
+using PdfSharp.Internal;
+using PdfSharp.Internal.OpenType;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Fonts.Internal;
+using PdfSharp.Fonts;
#if GDI
using System.Drawing;
using System.Drawing.Drawing2D;
@@ -9,9 +16,6 @@
using GdiFontStyle = System.Drawing.FontStyle;
#endif
#if WPF
-//using System.Windows;
-//using System.Windows.Documents;
-//using System.Windows.Media;
using WpfFontFamily = System.Windows.Media.FontFamily;
using WpfTypeface = System.Windows.Media.Typeface;
using WpfGlyphTypeface = System.Windows.Media.GlyphTypeface;
@@ -20,58 +24,44 @@
#if WUI
using Windows.UI.Xaml.Media;
#endif
-using Microsoft.Extensions.Logging;
-using PdfSharp.Fonts;
-using PdfSharp.Fonts.Internal;
-using PdfSharp.Fonts.OpenType;
-using PdfSharp.Internal;
-using PdfSharp.Logging;
namespace PdfSharp.Drawing
{
+ // TODO-7.0: Use OpenTypeGlyphTypeface as implementation of XGlyphTypeface.
+
///
/// Specifies a physical font face that corresponds to a font file on the disk or in memory.
///
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
public sealed class XGlyphTypeface
{
- // Implementation Notes
- //
- // * Each XGlyphTypeface can belong to one or more XFont objects.
- // * An XGlyphTypeface hold an XFontFamily.
- // * XGlyphTypeface hold a reference to an OpenTypeFontFace.
-
const string KeySuffix = ":TFK"; // Typeface Key
-
#if CORE
- XGlyphTypeface(string key, XFontFamily fontFamily, XFontSource fontSource, XStyleSimulations styleSimulations)
+ XGlyphTypeface(string key, XFontFamily fontFamily, XFontSource fontSource,
+ XStyleSimulations styleSimulations)
{
Key = key;
FontFamily = fontFamily;
FontSource = fontSource;
- FontFace = OpenTypeFontFace.CetOrCreateFrom(fontSource);
+ Debug.Assert(fontSource.OTFontFace != null);
+ //OTFontFace = OpenTypeFontFace.GetOrCreateFrom(fontSource.OTFontSource);
+ OTFontFace = fontSource.OTFontFace;
- // Check why it fails.
- //Debug.Assert(ReferenceEquals(FontSource.FontFace, FontFace));
-
StyleSimulations = styleSimulations;
Initialize();
}
#endif
#if GDI
- XGlyphTypeface(string key, XFontFamily fontFamily, XFontSource fontSource, XStyleSimulations styleSimulations, GdiFont gdiFont)
+ XGlyphTypeface(string key, XFontFamily fontFamily, XFontSource fontSource,
+ XStyleSimulations styleSimulations, GdiFont gdiFont)
{
Key = key;
FontFamily = fontFamily;
FontSource = fontSource;
-
- FontFace = OpenTypeFontFace.CetOrCreateFrom(fontSource);
- Debug.Assert(ReferenceEquals(FontSource.FontFace, FontFace));
-
+ OTFontFace = FontSource.OTFontFace;
_gdiFont = gdiFont;
-
StyleSimulations = styleSimulations;
Initialize();
}
@@ -83,16 +73,13 @@ public sealed class XGlyphTypeface
///
public XGlyphTypeface(XFontSource fontSource)
{
- string familyName = fontSource.FontFace.name.Name;
+ string familyName = fontSource.OTFontFace.name.OTFamilyName;
FontFamily = new XFontFamily(familyName, false);
- FontFace = fontSource.FontFace;
- IsBold = FontFace.os2.IsBold;
- IsItalic = FontFace.os2.IsItalic;
-
+ OTFontFace = fontSource.OTFontFace;
+ IsBold = OTFontFace.os2.IsBold;
+ IsItalic = OTFontFace.os2.IsItalic;
Key = ComputeGtfKey(familyName, IsBold, IsItalic);
- //_fontFamily =xfont FontFamilyCache.GetFamilyByName(familyName);
FontSource = fontSource;
-
Initialize();
}
#endif
@@ -104,13 +91,9 @@ public XGlyphTypeface(XFontSource fontSource)
FontFamily = fontFamily;
FontSource = fontSource;
StyleSimulations = styleSimulations;
-
- FontFace = OpenTypeFontFace.CetOrCreateFrom(fontSource);
- Debug.Assert(ReferenceEquals(FontSource.FontFace, FontFace));
-
+ OTFontFace = FontSource.OTFontFace;
WpfTypeface = wpfTypeface;
WpfGlyphTypeface = wpfGlyphTypeface;
-
Initialize();
}
#endif
@@ -123,7 +106,7 @@ public XGlyphTypeface(XFontSource fontSource)
_fontSource = fontSource;
_styleSimulations = styleSimulations;
- _fontFace = OpenTypeFontFace.CetOrCreateFrom(fontSource);
+ _fontFace = OpenTypeFontFace.GetOrCreateFromBytes(fontSource);
Debug.Assert(ReferenceEquals(_fontSource.FontFace, _fontFace));
//_wpfTypeface = wpfTypeface;
@@ -141,16 +124,15 @@ internal static XGlyphTypeface GetOrCreateFrom(string familyName, FontResolvingO
try
{
// Lock around TryGetGlyphTypeface and AddGlyphTypeface.
- Lock.EnterFontFactory();
- if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface))
+ Locks.EnterFontManagement();
+ var glyphTypefaceCache = PsGlobals.Global.Fonts.GlyphTypefaceCache;
+ if (glyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface))
{
// Just return existing one.
return glyphTypeface;
}
//// Resolve typeface by FontFactory. If no success, try fallback font resolver.
- //var fontResolverInfo = FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, false) ??
- // FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, true);
FontResolverInfo? fontResolverInfo = null;
try // Custom font resolvers may throw an exception.
{
@@ -176,7 +158,7 @@ internal static XGlyphTypeface GetOrCreateFrom(string familyName, FontResolvingO
}
void LogErrorBecauseFontResolverThrowsException(string name, Exception ex)
- => PdfSharpLogHost.Logger.LogError("A font resolver cannot resolve font family '{}' and throws an exception, " +
+ => PdfSharpLogHost.Logger.LogError("A font resolver cannot resolve font family '{familyName}' and throws an exception, " +
"but it must return null if the font cannot be resolved. Exception text: " + ex.Message, name);
if (fontResolverInfo == null)
@@ -194,7 +176,7 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex)
throw new InvalidOperationException($"No appropriate font found for family name '{familyName}'.");
}
#if GDI
- GdiFont gdiFont = default!;
+ GdiFont gdiFont = null!;
#endif
#if WPF
// ReSharper disable once TooWideLocalVariableScope
@@ -215,9 +197,6 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex)
#if CORE
// Get or create font family for custom font resolver retrieved font source.
fontFamily = XFontFamily.GetOrCreateFontFamily(familyName);
- //// Cannot come here
- //fontFamily = null;
- //Debug.Assert(false);
#endif
#if GDI
// Reuse GDI+ font from platform font resolver.
@@ -244,7 +223,8 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex)
}
// We have a valid font resolver info. That means we also have an XFontSource object loaded in the cache.
- XFontSource fontSource = FontFactory.GetFontSourceByFontName(fontResolverInfo.FaceName);
+ var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache;
+ XFontSource fontSource = fontSourceCache.GetFontSourceByFontName(fontResolverInfo.FaceName);
Debug.Assert(fontSource != null);
// Each font source already contains its OpenTypeFontFace.
@@ -260,9 +240,9 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex)
#if WUI
glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations);
#endif
- GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface);
+ glyphTypefaceCache.AddGlyphTypeface(glyphTypeface);
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
return glyphTypeface;
}
@@ -277,9 +257,10 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont)
try
{
// Lock around TryGetGlyphTypeface and AddGlyphTypeface.
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
string typefaceKey = ComputeGtfKey(gdiFont);
- if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface))
+ var glyphTypefaceCache = PsGlobals.Global.Fonts.GlyphTypefaceCache;
+ if (glyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface))
{
// We have the glyph typeface already in cache.
return glyphTypeface;
@@ -292,15 +273,16 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont)
// Check if styles must be simulated.
XStyleSimulations styleSimulations = XStyleSimulations.None;
- if (gdiFont.Bold && !fontSource.FontFace.os2.IsBold)
+ if (gdiFont.Bold && !fontSource.OTFontFace.os2.IsBold)
styleSimulations |= XStyleSimulations.BoldSimulation;
- if (gdiFont.Italic && !fontSource.FontFace.os2.IsItalic)
+ if (gdiFont.Italic && !fontSource.OTFontFace.os2.IsItalic)
styleSimulations |= XStyleSimulations.ItalicSimulation;
glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, styleSimulations, gdiFont);
- GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface);
+ //var glyphTypefaceCache = PsGlobals.Global.Fonts.GlyphTypefaceCache;
+ glyphTypefaceCache.AddGlyphTypeface(glyphTypeface);
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
return glyphTypeface;
}
@@ -324,7 +306,7 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont)
// Lock around TryGetGlyphTypeface and AddGlyphTypeface.
try
{
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
// Create WPF glyph typeface.
if (!wpfTypeface.TryGetGlyphTypeface(out var wpfGlyphTypeface))
@@ -343,7 +325,8 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont)
// ReSharper restore UnusedVariable
#endif
- if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out var glyphTypeface))
+ var glyphTypefaceCache = PsGlobals.Global.Fonts.GlyphTypefaceCache;
+ if (glyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out var glyphTypeface))
{
// We have the glyph typeface already in cache.
return glyphTypeface;
@@ -355,11 +338,11 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont)
glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource,
(XStyleSimulations)wpfGlyphTypeface.StyleSimulations,
wpfTypeface, wpfGlyphTypeface);
- GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface);
+ glyphTypefaceCache.AddGlyphTypeface(glyphTypeface);
return glyphTypeface;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
#endif
@@ -368,7 +351,7 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont)
///
public XFontFamily FontFamily { get; }
- internal OpenTypeFontFace FontFace { get; }
+ internal OpenTypeFontFace OTFontFace { get; }
///
/// Gets the font source of this glyph typeface.
@@ -377,51 +360,49 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont)
void Initialize()
{
- FamilyName = FontFace.name.Name;
- //if (String.IsNullOrEmpty(FaceName) || FaceName.StartsWith("?", StringComparison.Ordinal))
-#if TEST_CODE_
- if (!String.IsNullOrEmpty(FaceName))
- _ = typeof(int);
-#endif
- FaceName = FontFace.name.FullFontName;
- StyleName = FontFace.name.Style;
- DisplayName = FontFace.name.FullFontName;
+ FamilyName = OTFontFace.name.OTFamilyName;
+ FaceName = OTFontFace.name.OTSubfamilyName;
+ FontName = DisplayName = OTFontFace.name.OTFullFontName;
if (String.IsNullOrEmpty(DisplayName))
{
- DisplayName = FamilyName;
- if (!String.IsNullOrEmpty(StyleName))
- DisplayName += " (" + StyleName + ")";
+ FontName = DisplayName = FamilyName;
+ if (!String.IsNullOrEmpty(FaceName))
+ DisplayName += " (" + FaceName + ")";
}
// Bold, as defined in OS/2 table.
- IsBold = FontFace.os2.IsBold;
- // Debug.Assert(_isBold == (_fontFace.os2.usWeightClass > 400), "Check font weight.");
+ IsBold = OTFontFace.os2.IsBold;
// Italic, as defined in OS/2 table.
- IsItalic = FontFace.os2.IsItalic;
+ IsItalic = OTFontFace.os2.IsItalic;
}
///
- /// Gets the name of the font face. This can be a file name, an URI, or a GUID.
+ /// Gets the name of the font face. This can be a file name, a URI, or a GUID.
///
- internal string FaceName { get; private set; } = default!;
+ internal string FontName{ get; private set; } = null!; // Former FaceName
///
/// Gets the English family name of the font, for example "Arial".
///
- public string FamilyName { get; private set; } = default!;
+ public string FamilyName { get; private set; } = null!;
///
/// Gets the English subfamily name of the font,
/// for example "Bold".
///
- public string StyleName { get; private set; } = default!;
+ ///
+ /// Prior to PDFsharp 7 this function incorrectly returns the family.
+ /// Starting with PDFsharp 7 it returns the correct value.
+ /// This is a breaking change.
+ ///
+ public string FaceName { get; private set; } = null!;
///
/// Gets the English display name of the font,
/// for example "Arial italic".
///
- public string DisplayName { get; private set; } = default!;
+ public string DisplayName { get; private set; } = null!;
///
/// Gets a value indicating whether the font weight is bold.
@@ -492,9 +473,9 @@ internal static string ComputeGtfKey(string familyName, FontResolvingOptions fon
var italic = fontResolvingOptions.IsItalic;
var key = bold switch
{
- false when !italic => name + "/N/400/500" + simulationSuffix + KeySuffix,
- true when !italic => name + "/N/700/500" + simulationSuffix + KeySuffix,
- false when italic => name + "/I/400/500" + simulationSuffix + KeySuffix,
+ false when !italic => name + "/N/400/5" + simulationSuffix + KeySuffix,
+ true when !italic => name + "/N/700/5" + simulationSuffix + KeySuffix,
+ false when italic => name + "/I/400/5" + simulationSuffix + KeySuffix,
_ => name + "/I/700/500" + simulationSuffix + KeySuffix,
};
return key;
@@ -589,8 +570,8 @@ internal static string ComputeGtfKey(WpfGlyphTypeface wpfGlyphTypeface)
internal WpfTypeface? WpfTypeface { get; }
internal WpfGlyphTypeface? WpfGlyphTypeface { get; }
#endif
- internal void CheckVersion() => Globals.Global.Fonts.CheckVersion(_globalFontStorageVersion);
- readonly int _globalFontStorageVersion = Globals.Global.Fonts.Version;
+ internal void CheckVersion() => PsGlobals.Global.Fonts.CheckVersion(_globalFontStorageVersion);
+ readonly int _globalFontStorageVersion = PsGlobals.Global.Fonts.Version;
///
/// Gets the DebuggerDisplayAttribute text.
@@ -598,7 +579,7 @@ internal static string ComputeGtfKey(WpfGlyphTypeface wpfGlyphTypeface)
// ReSharper disable UnusedMember.Local
internal string DebuggerDisplay
// ReSharper restore UnusedMember.Local
- => Invariant($"{FamilyName} - {StyleName} ({FaceName})");
+ => Invariant($"{FamilyName} - {FaceName} ({FontName})");
}
}
@@ -721,5 +702,4 @@ Gets the designed weight of the font represented by the GlyphTypeface object.
XHeight
Gets the Western x-height relative to em size for the font represented by the GlyphTypeface object.
-
*/
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs
index 880ed3ef..60a8a67b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs
@@ -3,6 +3,16 @@
using System.Text;
using Microsoft.Extensions.Logging;
+using PdfSharp.Internal.OpenType;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Pdf.Internal;
+using PdfSharp.Pdf;
+using PdfSharp.Drawing.Pdf;
+using PdfSharp.Events;
+using PdfSharp.Fonts.Internal;
+using PdfSharp.Internal;
+using PdfSharp.Logging;
+using PdfSharp.Pdf.Advanced;
#if GDI
using System.Drawing.Drawing2D;
@@ -38,17 +48,9 @@
using SysSize = Windows.Foundation.Size;
using SysRect = Windows.Foundation.Rect;
#endif
-using PdfSharp.Pdf;
-using PdfSharp.Drawing.Pdf;
-using PdfSharp.Events;
-using PdfSharp.Fonts.Internal;
-using PdfSharp.Internal;
-using PdfSharp.Logging;
-//using PdfSharp.Internal;
-using PdfSharp.Pdf.Advanced;
-using PdfSharp.Pdf.Internal;
-#pragma warning disable 1587
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+#pragma warning disable 1587 // TODO_DOC: XML comment is not placed on a valid language element
// ReSharper disable UseNullPropagation
// ReSharper disable RedundantNameQualifier
// ReSharper disable UseNameofExpression
@@ -92,10 +94,10 @@ public sealed class XGraphics : IDisposable
// MigraDoc comes here when creating a MeasureContext.
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
gfx = Graphics.FromHwnd(IntPtr.Zero); // BUG_OLD: Use measure image
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
_gsStack = new GraphicsStateStack(this);
@@ -127,7 +129,7 @@ public sealed class XGraphics : IDisposable
break;
default:
- throw new NotImplementedException("unit");
+ throw new NotSupportedException($"Unit {pageUnit}");
}
_pageDirection = pageDirection;
@@ -168,7 +170,7 @@ public sealed class XGraphics : IDisposable
XGraphicsUnit.Millimeter => new XSize(XUnitPt.FromMillimeter(size.Width), XUnitPt.FromMillimeter(size.Height)),
XGraphicsUnit.Centimeter => new XSize(XUnitPt.FromCentimeter(size.Width), XUnitPt.FromCentimeter(size.Height)),
XGraphicsUnit.Presentation => new XSize(XUnitPt.FromPresentation(size.Width), XUnitPt.FromPresentation(size.Height)),
- _ => throw new NotImplementedException("unit")
+ _ => throw new NotSupportedException($"Unit {pageUnit}")
};
_pageDirection = pageDirection;
@@ -239,7 +241,7 @@ public sealed class XGraphics : IDisposable
break;
default:
- throw new NotImplementedException("unit");
+ throw new NotSupportedException($"Unit {pageUnit}");
}
_pageDirection = pageDirection;
@@ -291,7 +293,7 @@ public sealed class XGraphics : IDisposable
break;
default:
- throw new NotImplementedException("unit");
+ throw new NotSupportedException($"Unit {pageUnit}");
}
_pageDirection = pageDirection;
@@ -304,6 +306,10 @@ public sealed class XGraphics : IDisposable
///
XGraphics(PdfPage page, XGraphicsPdfPageOptions options, XGraphicsUnit pageUnit, XPageDirection pageDirection)
{
+#if DEBUG
+ if (page.IsDead)
+ _ = typeof(int);
+#endif
page.Owner.EnsureNotYetSaved();
if (page == null)
@@ -313,38 +319,38 @@ public sealed class XGraphics : IDisposable
throw new ArgumentException("You cannot draw on a page that is not owned by a PdfDocument object.", nameof(page));
if (page.RenderContent != null)
- throw new InvalidOperationException("An XGraphics object already exists for this page and must be disposed before a new one can be created.");
+ throw new InvalidOperationException("Another page content renderer object already exists for this page and must be disposed before a new one can be created.");
if (page.Owner.IsReadOnly)
throw new InvalidOperationException("Cannot create XGraphics for a page of a document that cannot be modified. Use PdfDocumentOpenMode.Modify.");
- _gsStack = new GraphicsStateStack(this);
+ _gsStack = new(this);
+
PdfContent? content;
switch (options)
{
- case XGraphicsPdfPageOptions.Replace:
- page.Contents.Elements.Clear();
- goto case XGraphicsPdfPageOptions.Append;
+ case XGraphicsPdfPageOptions.Append:
+ content = page.Contents.AppendContent();
+ break;
case XGraphicsPdfPageOptions.Prepend:
content = page.Contents.PrependContent();
break;
- case XGraphicsPdfPageOptions.Append:
- content = page.Contents.AppendContent();
- break;
+ case XGraphicsPdfPageOptions.Replace:
+ page.Contents.Elements.Clear();
+ goto case XGraphicsPdfPageOptions.Append;
default:
throw new InvalidOperationException();
}
page.RenderContent = content;
-
#if CORE
TargetContext = XGraphicTargetContext.CORE;
#endif
#if GDI
// _gfx is not needed anymore for drawing on a page.
- _gfx = default!;
+ _gfx = null!;
TargetContext = XGraphicTargetContext.GDI;
#endif
#if WPF
@@ -381,7 +387,7 @@ public sealed class XGraphics : IDisposable
break;
default:
- throw new NotImplementedException("unit");
+ throw new NotSupportedException($"Unit {pageUnit}");
}
_pageUnit = pageUnit;
_pageDirection = pageDirection;
@@ -434,7 +440,7 @@ public sealed class XGraphics : IDisposable
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
TargetContext = XGraphicTargetContext.GDI;
// If form.Owner is null create a meta file.
if (form.Owner == null!)
@@ -505,7 +511,7 @@ public sealed class XGraphics : IDisposable
_renderer = new PdfSharp.Drawing.Pdf.XGraphicsPdfRenderer(form, this);
_pageSize = form.Size;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
Initialize();
#endif
#if WPF && !GDI
@@ -592,7 +598,6 @@ public static XGraphics FromGraphics(Graphics graphics, XSize size, XGraphicsUni
PdfSharpLogHost.Logger.XGraphicsCreated("GDI+ Graphics object");
return gfx;
-
}
/////
@@ -660,7 +665,7 @@ public static XGraphics FromPdfPage(PdfPage page)
{
var gfx = new XGraphics(page, XGraphicsPdfPageOptions.Append, XGraphicsUnit.Point, XPageDirection.Downwards);
// #PDF-UA
- if (page.Owner._uaManager != null)
+ if (page.Owner.UAManager != null)
page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated });
PdfSharpLogHost.Logger.XGraphicsCreated("PDF page");
@@ -675,7 +680,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsUnit unit)
{
var gfx = new XGraphics(page, XGraphicsPdfPageOptions.Append, unit, XPageDirection.Downwards);
// #PDF-UA
- if (page.Owner._uaManager != null)
+ if (page.Owner.UAManager != null)
page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated });
PdfSharpLogHost.Logger.XGraphicsCreated("PDF page");
@@ -690,7 +695,7 @@ public static XGraphics FromPdfPage(PdfPage page, XPageDirection pageDirection)
{
var gfx = new XGraphics(page, XGraphicsPdfPageOptions.Append, XGraphicsUnit.Point, pageDirection);
// #PDF-UA
- if (page.Owner._uaManager != null)
+ if (page.Owner.UAManager != null)
page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated });
PdfSharpLogHost.Logger.XGraphicsCreated("PDF page");
@@ -705,7 +710,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option
{
var gfx = new XGraphics(page, options, XGraphicsUnit.Point, XPageDirection.Downwards);
// #PDF-UA
- if (page.Owner._uaManager != null)
+ if (page.Owner.UAManager != null)
page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated });
PdfSharpLogHost.Logger.XGraphicsCreated("PDF page");
@@ -720,7 +725,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option
{
var gfx = new XGraphics(page, options, XGraphicsUnit.Point, pageDirection);
// #PDF-UA
- if (page.Owner._uaManager != null)
+ if (page.Owner.UAManager != null)
page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated });
PdfSharpLogHost.Logger.XGraphicsCreated("PDF page");
@@ -735,7 +740,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option
{
XGraphics gfx = new XGraphics(page, options, unit, XPageDirection.Downwards);
// #PDF-UA
- if (page.Owner._uaManager != null)
+ if (page.Owner.UAManager != null)
page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated });
PdfSharpLogHost.Logger.XGraphicsCreated("PDF page");
@@ -750,7 +755,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option
{
var gfx = new XGraphics(page, options, unit, pageDirection);
// #PDF-UA
- if (page.Owner._uaManager != null)
+ if (page.Owner.UAManager != null)
page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated });
PdfSharpLogHost.Logger.XGraphicsCreated("PDF page");
@@ -763,7 +768,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option
///
public static XGraphics FromPdfForm(XPdfForm form)
{
- if (form.Gfx != null!)
+ if (form.Gfx != null)
return form.Gfx;
var gfx = new XGraphics(form, form.Owner.RenderEvents);
@@ -778,7 +783,7 @@ public static XGraphics FromPdfForm(XPdfForm form)
///
public static XGraphics FromForm(XForm form)
{
- if (form.Gfx != null!)
+ if (form.Gfx != null)
return form.Gfx;
var gfx = new XGraphics(form, form.Owner.RenderEvents);
@@ -833,7 +838,7 @@ public static XGraphics FromForm(XForm form)
///
void Initialize()
{
- _pageOrigin = new XPoint();
+ _pageOrigin = new();
double pageHeight = _pageSize.Height;
var targetPage = PdfPage;
@@ -854,7 +859,7 @@ void Initialize()
{
try
{
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
if (_gfx != null)
matrix = _gfx.Transform;
@@ -882,7 +887,7 @@ void Initialize()
_gfx.Transform = (GdiMatrix)matrix;
}
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
#endif
#if WPF
@@ -923,11 +928,14 @@ void Initialize()
matrix.TranslatePrepend(trimOffset.X, -trimOffset.Y);
DefaultViewMatrix = matrix;
- _transform = new XMatrix();
+ _transformationMatrix = new XMatrix();
}
///
/// Releases all resources used by this object.
+ /// PDFsharp disposes XGraphics objects automatically.
+ /// You must call Dispose if you want to create another XGraphics object or a
+ /// PDFsharp Graphics DrawingContext for this page.
///
public void Dispose() => Dispose(true);
@@ -953,14 +961,14 @@ void Dispose(bool disposing)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
// GDI+ requires this to disassociate it from metafiles.
if (_gfx != default!)
_gfx.Dispose();
_gfx = null!;
Metafile = null;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1005,7 +1013,7 @@ public XPageDirection PageDirection
{
// Is there really anybody who needs the concept of XPageDirection.Upwards?
if (value != XPageDirection.Downwards)
- throw new NotImplementedException("PageDirection must be XPageDirection.Downwards in current implementation.");
+ throw new NotSupportedException("PageDirection must be XPageDirection.Downwards in current implementation.");
}
}
readonly XPageDirection _pageDirection;
@@ -1020,7 +1028,7 @@ public XPoint PageOrigin
{
// Is there really anybody who needs to set the page origin?
if (value != new XPoint())
- throw new NotImplementedException("PageOrigin cannot be modified in current implementation.");
+ throw new NotSupportedException("PageOrigin cannot be modified in current implementation.");
}
}
XPoint _pageOrigin;
@@ -1031,10 +1039,6 @@ public XPoint PageOrigin
public XSize PageSize
{
get => _pageSize;
- //set
- //{
- // throw new NotImplementedException("PageSize cannot be modified in current implementation.");
- //}
}
XSize _pageSize;
XSize _pageSizePoints;
@@ -1115,10 +1119,10 @@ public void DrawLine(XPen pen, double x1, double y1, double x2, double y2)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawLine(pen.RealizeGdiPen(), (float)x1, (float)y1, (float)x2, (float)y2);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1170,10 +1174,10 @@ public void DrawLines(XPen pen, GdiPointF[] points)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawLines(pen.RealizeGdiPen(), points);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
_renderer?.DrawLines(pen, MakeXPointArray(points, 0, points.Length));
@@ -1199,10 +1203,10 @@ public void DrawLines(XPen pen, XPoint[] points)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawLines(pen.RealizeGdiPen(), XGraphics.MakePointFArray(points));
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1323,10 +1327,10 @@ public void DrawBezier(XPen pen, double x1, double y1, double x2, double y2,
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawBezier(pen.RealizeGdiPen(), (float)x1, (float)y1, (float)x2, (float)y2, (float)x3, (float)y3, (float)x4, (float)y4);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1403,10 +1407,10 @@ public void DrawBeziers(XPen pen, XPoint[] points)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawBeziers(pen.RealizeGdiPen(), MakePointFArray(points)!); // points is checked.
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1588,10 +1592,10 @@ public void DrawCurve(XPen pen, XPoint[] points, double tension)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawCurve(pen.RealizeGdiPen(), MakePointFArray(points)!, (float)tension);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1674,10 +1678,10 @@ public void DrawArc(XPen pen, double x, double y, double width, double height, d
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawArc(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1747,10 +1751,10 @@ public void DrawRectangle(XPen pen, double x, double y, double width, double hei
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawRectangle(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1816,10 +1820,10 @@ public void DrawRectangle(XBrush brush, double x, double y, double width, double
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.FillRectangle(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -1886,13 +1890,13 @@ public void DrawRectangle(XPen? pen, XBrush? brush, double x, double y, double w
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillRectangle(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height);
if (pen != null)
_gfx.DrawRectangle(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2016,13 +2020,13 @@ public void DrawRectangles(XPen? pen, XBrush? brush, Rectangle[] rectangles)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillRectangles(brush.RealizeGdiBrush(), rectangles);
if (pen != null)
_gfx.DrawRectangles(pen.RealizeGdiPen(), rectangles);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
if (_renderer != null)
{
@@ -2051,13 +2055,13 @@ public void DrawRectangles(XPen? pen, XBrush? brush, GdiRectF[] rectangles)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillRectangles(brush.RealizeGdiBrush(), rectangles);
if (pen != null)
_gfx.DrawRectangles(pen.RealizeGdiPen(), rectangles);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
if (_renderer != null)
{
@@ -2093,13 +2097,13 @@ public void DrawRectangles(XPen? pen, XBrush? brush, XRect[] rectangles)
GdiRectF[] rects = MakeRectangleFArray(rectangles, 0, rectangles.Length);
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillRectangles(brush.RealizeGdiBrush(), rects);
if (pen != null)
_gfx.DrawRectangles(pen.RealizeGdiPen(), rects);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2292,12 +2296,12 @@ public void DrawRoundedRectangle(XPen? pen, XBrush? brush, double x, double y, d
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
XGraphicsPath path = new XGraphicsPath();
path.AddRoundedRectangle(x, y, width, height, ellipseWidth, ellipseHeight);
DrawPath(pen, brush, path);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2362,10 +2366,10 @@ public void DrawEllipse(XPen pen, double x, double y, double width, double heigh
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawArc(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, 0, 360);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2426,10 +2430,10 @@ public void DrawEllipse(XBrush brush, double x, double y, double width, double h
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.FillEllipse(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2493,13 +2497,13 @@ public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double wid
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillEllipse(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height);
if (pen != null)
_gfx.DrawArc(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, 0, 360);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2588,10 +2592,10 @@ public void DrawPolygon(XPen pen, XPoint[] points)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawPolygon(pen.RealizeGdiPen(), MakePointFArray(points)!);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2656,10 +2660,10 @@ public void DrawPolygon(XBrush brush, XPoint[] points, XFillMode fillMode)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.FillPolygon(brush.RealizeGdiBrush(), MakePointFArray(points), (FillMode)fillMode);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2727,13 +2731,13 @@ public void DrawPolygon(XPen? pen, XBrush? brush, XPoint[] points, XFillMode fil
GdiPointF[] pts = MakePointFArray(points)!;
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillPolygon(brush.RealizeGdiBrush(), pts, (FillMode)fillMode);
if (pen != null)
_gfx.DrawPolygon(pen.RealizeGdiPen(), pts);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2800,10 +2804,10 @@ public void DrawPie(XPen pen, double x, double y, double width, double height, d
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawPie(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2861,10 +2865,10 @@ public void DrawPie(XBrush brush, double x, double y, double width, double heigh
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.FillPie(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -2921,13 +2925,13 @@ public void DrawPie(XPen? pen, XBrush? brush, double x, double y, double width,
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillPie(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle);
if (pen != null)
_gfx.DrawPie(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -3287,7 +3291,7 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, XFillMode
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillClosedCurve(brush.RealizeGdiBrush(), MakePointFArray(points)!, (FillMode)fillMode, (float)tension);
if (pen != null)
@@ -3296,7 +3300,7 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, XFillMode
_gfx.DrawClosedCurve(pen.RealizeGdiPen(), MakePointFArray(points)!, (float)tension, (FillMode)fillMode);
}
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -3353,10 +3357,10 @@ public void DrawPath(XPen pen, XGraphicsPath path)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.DrawPath(pen.RealizeGdiPen(), path.GdipPath);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -3387,10 +3391,10 @@ public void DrawPath(XBrush brush, XGraphicsPath path)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.FillPath(brush.RealizeGdiBrush(), path.GdipPath);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -3424,13 +3428,13 @@ public void DrawPath(XPen? pen, XBrush? brush, XGraphicsPath path)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (brush != null)
_gfx.FillPath(brush.RealizeGdiBrush(), path.GdipPath);
if (pen != null)
_gfx.DrawPath(pen.RealizeGdiPen(), path.GdipPath);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -3561,7 +3565,7 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdiRectF rect = layoutRectangle.ToRectangleF();
if (format.LineAlignment == XLineAlignment.BaseLine)
{
@@ -3578,7 +3582,7 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan
_gfx.DrawString(text, font.GdiFont, brush.RealizeGdiBrush(), rect,
format.RealizeGdiStringFormat());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -3644,7 +3648,6 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan
{
PdfSharpLogHost.Logger.LogInformation($"DrawString XLineAlignment.Center: y={y} test={test}");
}
-
#endif
break;
@@ -3704,7 +3707,6 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan
}
#endif
}
-
_renderer?.DrawString(text, font, brush, layoutRectangle, format);
}
@@ -3943,7 +3945,7 @@ public void DrawImage(XImage image, double x, double y)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (image._gdiImage != null)
{
InterpolationMode interpolationMode = InterpolationMode.Invalid;
@@ -3966,7 +3968,7 @@ public void DrawImage(XImage image, double x, double y)
//_gfx.DrawLine(Pens.Red, (float)(x + width), (float)y, (float)x, (float)(y + height));
}
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -4036,7 +4038,7 @@ public void DrawImage(XImage image, double x, double y, double width, double hei
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (image._gdiImage != null)
{
InterpolationMode interpolationMode = InterpolationMode.Invalid;
@@ -4069,7 +4071,7 @@ public void DrawImage(XImage image, double x, double y, double width, double hei
}
}
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -4154,7 +4156,7 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (image._gdiImage != null!)
{
InterpolationMode interpolationMode = InterpolationMode.Invalid;
@@ -4178,7 +4180,7 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit
DrawMissingImageRect(new XRect(destRect.X, destRect.Y, destRect.Width, destRect.Height));
}
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -4223,7 +4225,7 @@ void DrawMissingImageRect(XRect rect)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
float x = (float)rect.X;
float y = (float)rect.Y;
float width = (float)rect.Width;
@@ -4232,7 +4234,7 @@ void DrawMissingImageRect(XRect rect)
_gfx.DrawLine(Pens.Red, x, y, x + width, y + height);
_gfx.DrawLine(Pens.Red, x + width, y, x, y + height);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -4344,8 +4346,10 @@ public XGraphicsState Save()
if (TargetContext == XGraphicTargetContext.CORE || TargetContext == XGraphicTargetContext.NONE)
{
xState = new XGraphicsState();
- InternalGraphicsState iState = new InternalGraphicsState(this, xState);
- iState.Transform = _transform;
+ InternalGraphicsState iState = new InternalGraphicsState(this, xState)
+ {
+ Transform = _transformationMatrix
+ };
_gsStack.Push(iState);
}
else
@@ -4358,13 +4362,13 @@ public XGraphicsState Save()
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
xState = new XGraphicsState(_gfx != null! ? _gfx.Save() : null);
InternalGraphicsState iState = new InternalGraphicsState(this, xState);
- iState.Transform = _transform;
+ iState.Transform = _transformationMatrix;
_gsStack.Push(iState);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -4372,7 +4376,7 @@ public XGraphicsState Save()
{
xState = new XGraphicsState();
InternalGraphicsState iState = new InternalGraphicsState(this, xState);
- iState.Transform = _transform;
+ iState.Transform = _transformationMatrix;
_gsStack.Push(iState);
}
#endif
@@ -4394,7 +4398,7 @@ public void Restore(XGraphicsState state)
if (TargetContext == XGraphicTargetContext.CORE)
{
_gsStack.Restore(state.InternalState);
- _transform = state.InternalState.Transform;
+ _transformationMatrix = state.InternalState.Transform;
}
#endif
#if GDI
@@ -4402,20 +4406,20 @@ public void Restore(XGraphicsState state)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gsStack.Restore(state.InternalState);
if (_gfx != null!)
_gfx.Restore(state.GdiState!); // BUG_OLD NRT
- _transform = state.InternalState.Transform;
+ _transformationMatrix = state.InternalState.Transform;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
if (TargetContext == XGraphicTargetContext.WPF)
{
_gsStack.Restore(state.InternalState);
- _transform = state.InternalState.Transform;
+ _transformationMatrix = state.InternalState.Transform;
}
#endif
_renderer?.Restore(state);
@@ -4494,13 +4498,13 @@ public XGraphicsContainer BeginContainer(XRect dstRect, XRect srcRect, XGraphics
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GraphicsState? graphicsState = null;
if (_gfx != null!)
graphicsState = _gfx.Save();
xContainer = new XGraphicsContainer(graphicsState);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -4512,7 +4516,7 @@ public XGraphicsContainer BeginContainer(XRect dstRect, XRect srcRect, XGraphics
throw new InvalidOperationException(nameof(xContainer));
InternalGraphicsState iState = new InternalGraphicsState(this, xContainer);
- iState.Transform = _transform;
+ iState.Transform = _transformationMatrix;
_gsStack.Push(iState);
@@ -4549,17 +4553,17 @@ public void EndContainer(XGraphicsContainer container)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
// GdiState cannot be null if _gfx is not null.
_gfx.Restore(container.GdiState!);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
// Nothing to do.
#endif
- _transform = container.InternalState.Transform;
+ _transformationMatrix = container.InternalState.Transform;
_renderer?.EndContainer(container);
}
@@ -4596,10 +4600,10 @@ public XSmoothingMode SmoothingMode
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return (XSmoothingMode)_gfx.SmoothingMode;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -4619,10 +4623,10 @@ public XSmoothingMode SmoothingMode
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.SmoothingMode = (SmoothingMode)value;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -4842,21 +4846,18 @@ public void MultiplyTransform(XMatrix matrix, XMatrixOrder order)
/// The transformation matrix cannot be set. Instead use Save/Restore or BeginContainer/EndContainer to
/// save the state before Transform is called and later restore to the previous transform.
///
- public XMatrix Transform
- {
- get { return _transform; }
- }
+ public XMatrix Transform => _transformationMatrix;
///
/// Applies a new transformation to the current transformation matrix.
///
void AddTransform(XMatrix transform, XMatrixOrder order)
{
- XMatrix matrix = _transform;
+ XMatrix matrix = _transformationMatrix;
matrix.Multiply(transform, order);
- _transform = matrix;
+ _transformationMatrix = matrix;
matrix = DefaultViewMatrix;
- matrix.Multiply(_transform, XMatrixOrder.Prepend);
+ matrix.Multiply(_transformationMatrix, XMatrixOrder.Prepend);
#if CORE_ // No concept of target context in Core build.
if (TargetContext == XGraphicTargetContext.CORE)
{
@@ -4870,10 +4871,10 @@ void AddTransform(XMatrix transform, XMatrixOrder order)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.Transform = (GdiMatrix)matrix;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
}
#endif
@@ -4961,10 +4962,10 @@ public void IntersectClip(XGraphicsPath path)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gfx.SetClip(path.GdipPath, CombineMode.Intersect);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF && !GDI
@@ -5034,11 +5035,16 @@ public XGraphicsInternals Internals
///
/// (Under construction. May change in future versions.)
///
+#if true_ // Change to 'true_' if code does not compile.
public SpaceTransformer Transformer
- {
- get { return _transformer ??= new SpaceTransformer(this); }
- }
- SpaceTransformer _transformer = default!;
+ => field ??= new SpaceTransformer(this);
+
+#else
+ public SpaceTransformer Transformer
+ => _transformer ??= new SpaceTransformer(this);
+
+ SpaceTransformer? _transformer;
+#endif
#endregion
@@ -5281,7 +5287,7 @@ internal void AppendToContentStream(string str)
///
/// The transformation matrix from XGraphics world space to page unit space.
///
- XMatrix _transform;
+ XMatrix _transformationMatrix;
///
/// The graphics state stack.
@@ -5331,6 +5337,15 @@ public Graphics Graphics
get { return _gfx._gfx; }
}
#endif
+ public string? RealizedFontName
+ {
+ get
+ {
+ if (_gfx._renderer is XGraphicsPdfRenderer renderer)
+ return renderer.PdfGraphicsState.RealizedFontName;
+ return null;
+ }
+ }
///
/// Gets the content string builder of XGraphicsPdfRenderer, if it exists.
@@ -5384,7 +5399,7 @@ public XRect WorldToDefaultPage(XRect rect)
double ymin = Math.Min(Math.Min(points[0].Y, points[1].Y), Math.Min(points[2].Y, points[3].Y));
double ymax = Math.Max(Math.Max(points[0].Y, points[1].Y), Math.Max(points[2].Y, points[3].Y));
- return new XRect(xmin, ymin, xmax - xmin, ymax - ymin);
+ return new(xmin, ymin, xmax - xmin, ymax - ymin);
}
///
@@ -5393,7 +5408,7 @@ public XRect WorldToDefaultPage(XRect rect)
public XPoint WorldToDefaultPage(XPoint point)
{
XMatrix matrix = _gfx.Transform;
- matrix.Transform(point);
+ matrix.Transform(point); // StL/25-11-22: This does nothing!
double height = _gfx.PageSize.Height;
point.Y = height - point.Y;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs
index 1d28e5f7..b4288c80 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs
@@ -1,6 +1,9 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Fonts.Internal;
#if GDI
using System.Drawing.Drawing2D;
#endif
@@ -18,8 +21,6 @@
using SysSize = Windows.Foundation.Size;
using SysRect = Windows.Foundation.Rect;
#endif
-using PdfSharp.Internal;
-using PdfSharp.Fonts.Internal;
namespace PdfSharp.Drawing
{
@@ -39,10 +40,10 @@ public XGraphicsPath()
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath = new GraphicsPath();
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry = new PathGeometry();
@@ -58,10 +59,10 @@ public XGraphicsPath(PointF[] points, byte[] types, XFillMode fillMode)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath = new GraphicsPath(points, types, (FillMode)fillMode);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF // Is true only in Hybrid build.
_pathGeometry = new PathGeometry();
@@ -143,10 +144,10 @@ public XGraphicsPath Clone()
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
path.GdipPath = (GraphicsPath)GdipPath.Clone();
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
path.PathGeometry = PathGeometry.Clone();
@@ -224,10 +225,10 @@ public void AddLine(double x1, double y1, double x2, double y2)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddLine((float)x1, (float)y1, (float)x2, (float)y2);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
PathFigure figure = CurrentPathFigure;
@@ -307,10 +308,10 @@ public void AddLines(XPoint[] points)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddLines(XGraphics.MakePointFArray(points));
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
PathFigure figure = CurrentPathFigure;
@@ -397,10 +398,10 @@ public void AddBezier(double x1, double y1, double x2, double y2, double x3, dou
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddBezier((float)x1, (float)y1, (float)x2, (float)y2, (float)x3, (float)y3, (float)x4, (float)y4);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
PathFigure figure = CurrentPathFigure;
@@ -495,10 +496,10 @@ public void AddBeziers(XPoint[] points)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddBeziers(XGraphics.MakePointFArray(points));
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
PathFigure figure = CurrentPathFigure;
@@ -621,10 +622,10 @@ public void AddCurve(XPoint[] points, double tension)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddCurve(XGraphics.MakePointFArray(points), (float)tension);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
tension /= 3;
@@ -699,10 +700,10 @@ public void AddCurve(XPoint[] points, int offset, int numberOfSegments, double t
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddCurve(XGraphics.MakePointFArray(points), offset, numberOfSegments, (float)tension);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
throw new NotImplementedException("AddCurve not yet implemented.");
@@ -750,10 +751,10 @@ public void AddArc(double x, double y, double width, double height, double start
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddArc((float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
PathFigure figure = CurrentPathFigure;
@@ -866,7 +867,7 @@ public void AddRectangle(XRect rect)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
// If rect is empty GDI+ removes the rect from the path.
// This is not intended if the path is used for clipping.
// See http://forum.pdfsharp.net/viewtopic.php?p=9433#p9433
@@ -877,7 +878,7 @@ public void AddRectangle(XRect rect)
GdipPath.AddLines([rect.TopLeft.ToPointF(), rect.TopRight.ToPointF(), rect.BottomRight.ToPointF(), rect.BottomLeft.ToPointF()]);
GdipPath.CloseFigure();
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
StartFigure();
@@ -928,10 +929,10 @@ public void AddRectangles(Rectangle[] rects)
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddRectangles(rects);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
@@ -947,10 +948,10 @@ public void AddRectangles(RectangleF[] rects)
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddRectangles(rects);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
@@ -968,10 +969,10 @@ public void AddRectangles(XRect[] rects)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddRectangle(rects[idx].ToRectangleF());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
StartFigure();
@@ -1095,7 +1096,7 @@ public void AddRoundedRectangle(double x, double y, double width, double height,
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.StartFigure();
GdipPath.AddArc((float)(x + width - ellipseWidth), (float)y, (float)ellipseWidth, (float)ellipseHeight, -90, 90);
GdipPath.AddArc((float)(x + width - ellipseWidth), (float)(y + height - ellipseHeight), (float)ellipseWidth, (float)ellipseHeight, 0, 90);
@@ -1103,7 +1104,7 @@ public void AddRoundedRectangle(double x, double y, double width, double height,
GdipPath.AddArc((float)x, (float)y, (float)ellipseWidth, (float)ellipseHeight, 180, 90);
GdipPath.CloseFigure();
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
double ex = ellipseWidth / 2;
@@ -1253,10 +1254,10 @@ public void AddEllipse(double x, double y, double width, double height)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddEllipse((float)x, (float)y, (float)width, (float)height);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
#if true
@@ -1300,10 +1301,10 @@ public void AddPolygon(System.Drawing.Point[] points)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddPolygon(points);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
@@ -1331,10 +1332,10 @@ public void AddPolygon(PointF[] points)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddPolygon(points);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
@@ -1357,10 +1358,10 @@ public void AddPolygon(XPoint[] points)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddPolygon(XGraphics.MakePointFArray(points));
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
#if true
@@ -1394,10 +1395,10 @@ public void AddPie(Rectangle rect, double startAngle, double sweepAngle)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddPie(rect, (float)startAngle, (float)sweepAngle);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
@@ -1431,10 +1432,10 @@ public void AddPie(double x, double y, double width, double height, double start
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddPie((float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
const string message = "AddPie: This operation is not yet implemented in WPF build.";
@@ -1532,10 +1533,10 @@ public void AddClosedCurve(XPoint[] points, double tension)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddClosedCurve(XGraphics.MakePointFArray(points), (float)tension);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
tension /= 3;
@@ -1573,10 +1574,10 @@ public void AddPath(XGraphicsPath path, bool connect)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddPath(path.GdipPath, connect);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry.AddGeometry(path.PathGeometry);
@@ -1646,10 +1647,10 @@ public void AddString(string s, XFontFamily family, XFontStyleEx style, double e
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddString(s, family.GdiFamily, (int)style, (float)emSize, p, format.RealizeGdiStringFormat());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
if (family.WpfFamily == null)
@@ -1726,10 +1727,10 @@ public void AddString(string s, XFontFamily family, XFontStyleEx style, double e
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddString(s, family.GdiFamily, (int)style, (float)emSize, rect, format.RealizeGdiStringFormat());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
///
@@ -1745,10 +1746,10 @@ public void AddString(string s, XFontFamily family, XFontStyleEx style, double e
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddString(s, family.GdiFamily, (int)style, (float)emSize, layoutRect, format.RealizeGdiStringFormat());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
///
@@ -1828,10 +1829,10 @@ public void AddString(string s, XFontFamily family, XFontStyleEx style, double e
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.AddString(s, family.GdiFamily, (int)style, (float)emSize, rect, format.RealizeGdiStringFormat());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF && !GDI
if (family.WpfFamily == null)
@@ -1972,10 +1973,10 @@ public void CloseFigure()
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.CloseFigure();
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathFigure figure = PeekCurrentFigure;
@@ -1995,10 +1996,10 @@ public void StartFigure()
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.StartFigure();
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathFigure figure = CurrentPathFigure;
@@ -2027,10 +2028,10 @@ public XFillMode FillMode
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.FillMode = (FillMode)value;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry.FillRule = value == XFillMode.Winding ? FillRule.Nonzero : FillRule.EvenOdd;
@@ -2054,10 +2055,10 @@ public void Flatten()
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.Flatten();
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry = PathGeometry.GetFlattenedPathGeometry();
@@ -2076,10 +2077,10 @@ public void Flatten(XMatrix matrix)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.Flatten(matrix.ToGdiMatrix());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry = PathGeometry.GetFlattenedPathGeometry();
@@ -2099,10 +2100,10 @@ public void Flatten(XMatrix matrix, double flatness)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.Flatten(matrix.ToGdiMatrix(), (float)flatness);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry = PathGeometry.GetFlattenedPathGeometry();
@@ -2127,10 +2128,10 @@ public void Widen(XPen pen)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.Widen(pen.RealizeGdiPen());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry = PathGeometry.GetWidenedPathGeometry(pen.RealizeWpfPen());
@@ -2152,10 +2153,10 @@ public void Widen(XPen pen, XMatrix matrix)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.Widen(pen.RealizeGdiPen(), matrix.ToGdiMatrix());
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry = PathGeometry.GetWidenedPathGeometry(pen.RealizeWpfPen());
@@ -2174,10 +2175,10 @@ public void Widen(XPen pen, XMatrix matrix, double flatness)
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
GdipPath.Widen(pen.RealizeGdiPen(), matrix.ToGdiMatrix(), (float)flatness);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF || WUI
PathGeometry = PathGeometry.GetWidenedPathGeometry(pen.RealizeWpfPen());
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs
index ea2ddbdd..7873481e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs
@@ -1,6 +1,13 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Internal.Imaging;
+using PdfSharp.Logging;
+using PdfSharp.Pdf.IO;
+using PdfSharp.Pdf.Advanced;
#if CORE
// Nothing to import.
#endif
@@ -8,7 +15,6 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
-using PdfSharp.Internal;
#endif
#if WPF
using System.Text;
@@ -17,9 +23,6 @@
#if WUI
using Windows.UI.Xaml.Media.Imaging;
#endif
-using PdfSharp.Drawing.Internal;
-using PdfSharp.Pdf.IO;
-using PdfSharp.Pdf.Advanced;
namespace PdfSharp.Drawing
{
@@ -39,10 +42,10 @@ public class XImage : IDisposable
{
// The hierarchy is adapted to WPF
//
- // XImage <-- ImageSource
+ // XImage <-- ImageSource
// XForm
// PdfForm
- // XBitmapSource <-- BitmapSource
+ // XBitmapSource <-- BitmapSource
// XBitmapImage <-- BitmapImage
// ???
@@ -77,6 +80,7 @@ protected XImage()
///
XImage(Image image)
{
+ // Procedure -> Create ImportedImage
_gdiImage = image;
#if WPF // Is defined in hybrid build.
_wpfImage = ImageHelper.CreateBitmapSource(image);
@@ -155,38 +159,40 @@ public static BitmapImage BitmapFromUri(Uri uri)
#endif
_path = path;
- //FileStream file = new FileStream(filename, FileMode.Open);
- //BitsLength = (int)file.Length;
- //Bits = new byte[BitsLength];
- //file.Read(Bits, 0, BitsLength);
- //file.Close();
+ // Use ImageImporter for supported formats.
+ if (TryImportImage(path, out var image))
+ {
+ _importedImage = image;
+ Initialize();
+ return;
+ }
+
#if GDI
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gdiImage = Image.FromFile(path);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
_wpfImage = BitmapFromUri(new Uri(path));
#endif
-#if true_
- float vres = image.VerticalResolution;
- float hres = image.HorizontalResolution;
- SizeF size = image.PhysicalDimension;
- int flags = image.Flags;
- Size sz = image.Size;
- GraphicsUnit units = GraphicsUnit.Millimeter;
- RectangleF rect = image.GetBounds(ref units);
- int width = image.Width;
-#endif
Initialize();
}
XImage(Stream stream)
{
+ // Use ImageImporter for supported formats.
+ if (TryImportImage(stream, out var image))
+ {
+ _importedImage = image;
+ // We do not store stream here because ImageImporter gets all information we need.
+ Initialize();
+ return;
+ }
+
//// Create a dummy unique path.
//_path = "*" + Guid.NewGuid().ToString("B");
@@ -195,10 +201,10 @@ public static BitmapImage BitmapFromUri(Uri uri)
// Create a GDI+ image.
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gdiImage = Image.FromStream(stream);
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if WPF
// Create a WPF BitmapImage.
@@ -318,6 +324,7 @@ public static XImage FromBitmapImageStreamThatCannotSeek(Stream stream)
#endif
#if CORE
+
///
/// Creates an image from the specified file.
///
@@ -327,10 +334,16 @@ public static XImage FromFile(string path)
if (PdfReader.TestPdfFile(path) > 0)
return new XPdfForm(path);
- var ii = ImageImporter.GetImageImporter();
- var i = ii.ImportImage(path) ?? throw new InvalidOperationException("Unsupported image format.");
+ PdfSharp.Internal.Imaging.ImportedImage ii;
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ // We can use FromStream, now that we have a stream.
+ var success = ToBeNamed.TryImportImage(stream, out ii!);
+ if (!success)
+ throw new InvalidOperationException("Unsupported image format.");
+ }
- var image = new XImage(i);
+ var image = new XImage(ii);
image._path = path;
return image;
}
@@ -351,10 +364,11 @@ public static XImage FromStream(Stream stream)
if (PdfReader.TestPdfFile(stream) > 0)
return new XPdfForm(stream);
- var ii = ImageImporter.GetImageImporter();
- var i = ii.ImportImage(stream) ?? throw new InvalidOperationException("Unsupported image format.");
+ var success = ToBeNamed.TryImportImage(stream, out var ii);
+ if (!success)
+ throw new InvalidOperationException("Unsupported image format.");
- XImage image = new XImage(i);
+ XImage image = new XImage(ii!);
image._stream = stream;
return image;
}
@@ -371,10 +385,12 @@ public static XImage FromBitmapImageStreamThatCannotSeek(Stream stream)
if (stream.CanSeek)
throw new InvalidOperationException("Use this function only for streams that do not support Seek.");
- var ii = ImageImporter.GetImageImporter();
- var i = ii.ImportImage(stream) ?? throw new InvalidOperationException("Unsupported image format.");
+ var worker = new StreamReaderWorker(stream);
+ var success = ToBeNamed.TryImportImage(worker.Data, out var i);
+ if (!success)
+ throw new InvalidOperationException("Unsupported image format.");
- XImage image = new XImage(i);
+ XImage image = new XImage(i!);
image._stream = stream;
return image;
}
@@ -385,7 +401,7 @@ public static XImage FromBitmapImageStreamThatCannotSeek(Stream stream)
/// Creates an image from the specified file.
///
/// The path to a BMP, PNG, GIF, JPEG, TIFF, or PDF file.
- /// Uses an platform-independent implementation if set to true.
+ /// Uses a platform-independent implementation if set to true.
/// The platform-dependent implementation, if available, will support more image formats.
public static XImage FromFile(string path, bool platformIndependent)
{
@@ -394,10 +410,12 @@ public static XImage FromFile(string path, bool platformIndependent)
// TODO_OLD: Check PDF file.
- var ii = ImageImporter.GetImageImporter();
- var i = ii.ImportImage(path) ?? throw new InvalidOperationException("Unsupported image format."); ;
+ var reader = new StreamReaderWorker(path);
+ var success = ToBeNamed.TryImportImage(reader.Data, out var img);
+ if (!success)
+ throw new InvalidOperationException("Unsupported image format."); ;
- var image = new XImage(i);
+ var image = new XImage(img!);
image._path = path;
return image;
}
@@ -406,7 +424,7 @@ public static XImage FromFile(string path, bool platformIndependent)
/// Creates an image from the specified stream.
///
/// The stream containing a BMP, PNG, GIF, JPEG, TIFF, or PDF file.
- /// Uses an platform-independent implementation if set to true.
+ /// Uses a platform-independent implementation if set to true.
/// The platform-dependent implementation, if available, will support more image formats.
public static XImage FromStream(Stream stream, bool platformIndependent)
{
@@ -418,10 +436,11 @@ public static XImage FromStream(Stream stream, bool platformIndependent)
if (stream == null)
throw new ArgumentNullException(nameof(stream));
- var ii = ImageImporter.GetImageImporter();
- var i = ii.ImportImage(stream) ?? throw new InvalidOperationException("Unsupported image format.");
+ var success = ToBeNamed.TryImportImage(stream, out var img);
+ if (!success)
+ throw new InvalidOperationException("Unsupported image format.");
- XImage image = new XImage(i);
+ XImage image = new XImage(img!);
image._stream = stream;
return image;
}
@@ -471,7 +490,7 @@ internal void Initialize()
if (_importedImage != null)
{
// In PDF there are two formats: JPEG and PDF bitmap.
- if (_importedImage is ImportedImageJpeg)
+ if (_importedImage is ImportedJpegImage)
_format = XImageFormat.Jpeg;
else
_format = XImageFormat.Png;
@@ -487,10 +506,10 @@ internal void Initialize()
string guid;
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
guid = _gdiImage.RawFormat.Guid.ToString("B").ToUpper();
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
switch (guid)
{
@@ -769,7 +788,7 @@ internal static bool ReadJpegFile(string filename, int maxRead, ref byte[]? imag
imageBits[9] == 0x46 &&
imageBits[10] == 0x0*/)
{
- // HACK_OLD: store the file in PDF if extension matches ...
+ // HACK_OLD: store the file in PDF if extension matches...
return null;
}
return false;
@@ -804,11 +823,11 @@ protected virtual void Dispose(bool disposing)
{
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
_gdiImage.Dispose();
_gdiImage = null!;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
}
#endif
#if WPF
@@ -828,7 +847,7 @@ public virtual double Width
{
#if CORE || GDI || WPF
if (_importedImage != null)
- return _importedImage.Information.Width;
+ return _importedImage.PixelWidth;
#endif
#if CORE
return 100;
@@ -836,10 +855,10 @@ public virtual double Width
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.Width;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
double gdiWidth = _gdiImage.Width;
@@ -869,8 +888,7 @@ public virtual double Height
{
#if CORE || GDI || WPF
if (_importedImage != null)
- return _importedImage.Information.Height;
-
+ return _importedImage.PixelHeight;
#endif
#if CORE
return 100;
@@ -878,10 +896,10 @@ public virtual double Height
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.Height;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
double gdiHeight = _gdiImage.Height;
@@ -925,12 +943,7 @@ public virtual double PointWidth
#if CORE || GDI || WPF
if (_importedImage != null)
{
- if (_importedImage.Information.HorizontalDPM > 0)
- return _importedImage.Information.Width * FactorDPM72 / _importedImage.Information.HorizontalDPM;
- if (_importedImage.Information.HorizontalDPI > 0)
- return _importedImage.Information.Width * 72 / _importedImage.Information.HorizontalDPI;
- // Assume 72 DPI if information not available.
- return _importedImage.Information.Width;
+ return _importedImage.Width;
}
#endif
#if CORE
@@ -939,10 +952,10 @@ public virtual double PointWidth
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.Width * 72 / _gdiImage.HorizontalResolution;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
double gdiWidth = _gdiImage.Width * 72 / _gdiImage.HorizontalResolution;
@@ -951,9 +964,6 @@ public virtual double PointWidth
Debug.Assert(DoubleUtil.AreRoughlyEqual(gdiWidth, wpfWidth, 5));
return wpfWidth;
#endif
- //#if GDI && !WPF
- // return _gdiImage.Width * 72 / _gdiImage.HorizontalResolution;
- //#endif
#if WPF && !GDI
Debug.Assert(Math.Abs(_wpfImage.PixelWidth * 72 / _wpfImage.DpiX - _wpfImage.Width * (72.0 / 96.0)) < 0.001);
return _wpfImage.Width * (72.0 / 96.0);
@@ -976,12 +986,7 @@ public virtual double PointHeight
#if CORE || GDI || WPF
if (_importedImage != null)
{
- if (_importedImage.Information.VerticalDPM > 0)
- return _importedImage.Information.Height * FactorDPM72 / _importedImage.Information.VerticalDPM;
- if (_importedImage.Information.VerticalDPI > 0)
- return _importedImage.Information.Height * 72 / _importedImage.Information.VerticalDPI;
- // Assume 72 DPI if information not available.
- return _importedImage.Information.Height;
+ return _importedImage.Height;
}
#endif
#if CORE
@@ -990,10 +995,10 @@ public virtual double PointHeight
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.Height * 72 / _gdiImage.HorizontalResolution;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
double gdiHeight = _gdiImage.Height * 72 / _gdiImage.HorizontalResolution;
@@ -1001,9 +1006,6 @@ public virtual double PointHeight
Debug.Assert(DoubleUtil.AreRoughlyEqual(gdiHeight, wpfHeight, 5));
return wpfHeight;
#endif
- //#if GDI && !WPF
- // return _gdiImage.Height * 72 / _gdiImage.HorizontalResolution;
- //#endif
#if WPF && !GDI
Debug.Assert(Math.Abs(_wpfImage.PixelHeight * 72 / _wpfImage.DpiY - _wpfImage.Height * (72.0 / 96.0)) < 0.001);
return _wpfImage.Height * (72.0 / 96.0);
@@ -1023,7 +1025,7 @@ public virtual int PixelWidth
{
#if CORE || GDI || WPF
if (_importedImage != null)
- return (int)_importedImage.Information.Width;
+ return (int)_importedImage.PixelWidth;
#endif
#if CORE
return 100;
@@ -1031,10 +1033,10 @@ public virtual int PixelWidth
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.Width;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
int gdiWidth = _gdiImage.Width;
@@ -1042,9 +1044,6 @@ public virtual int PixelWidth
Debug.Assert(gdiWidth == wpfWidth);
return wpfWidth;
#endif
- //#if GDI && !WPF
- // return _gdiImage.Width;
- //#endif
#if WPF && !GDI
return _wpfImage.PixelWidth;
#endif
@@ -1063,7 +1062,7 @@ public virtual int PixelHeight
{
#if CORE || GDI || WPF
if (_importedImage != null)
- return (int)_importedImage.Information.Height;
+ return (int)_importedImage.PixelHeight;
#endif
#if CORE
return 100;
@@ -1071,10 +1070,10 @@ public virtual int PixelHeight
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.Height;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
int gdiHeight = _gdiImage.Height;
@@ -1109,13 +1108,7 @@ public virtual double HorizontalResolution
#if CORE || GDI || WPF
if (_importedImage != null)
{
- if (_importedImage.Information.HorizontalDPI > 0)
- return _importedImage.Information.HorizontalDPI;
- if (_importedImage.Information.HorizontalDPM > 0)
- return _importedImage.Information.HorizontalDPM / FactorDPM;
- if (_importedImage.Information.DefaultDPI > 0)
- return _importedImage.Information.DefaultDPI;
- return 96;
+ return _importedImage.DpiX;
}
#endif
#if CORE
@@ -1124,10 +1117,10 @@ public virtual double HorizontalResolution
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.HorizontalResolution;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
double gdiResolution = _gdiImage.HorizontalResolution;
@@ -1157,13 +1150,7 @@ public virtual double VerticalResolution
#if CORE || GDI || WPF
if (_importedImage != null)
{
- if (_importedImage.Information.VerticalDPI > 0)
- return (double)_importedImage.Information.VerticalDPI;
- if (_importedImage.Information.VerticalDPM > 0)
- return (double)(_importedImage.Information.VerticalDPM / FactorDPM);
- if (_importedImage.Information.DefaultDPI > 0)
- return (double)_importedImage.Information.DefaultDPI;
- return 96;
+ return _importedImage.DpiY;
}
#endif
#if CORE
@@ -1172,10 +1159,10 @@ public virtual double VerticalResolution
#if GDI && !WPF
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
return _gdiImage.VerticalResolution;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
#endif
#if GDI && WPF
double gdiResolution = _gdiImage.VerticalResolution;
@@ -1209,6 +1196,40 @@ public virtual bool Interpolate
XImageFormat? _format;
+ static bool TryImportImage(string path,
+ [MaybeNullWhen(false)] out ImportedImage image)
+ {
+ image = null!;
+ try
+ {
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ var success = ToBeNamed.TryImportImage(stream, out image!);
+ if (!success)
+ return false;
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ PdfSharpLogHost.Logger.LogWarning(
+ $"Unhandled exception in TryImportImage, maybe caused by an unsupported image format. {ex.ToString()}");
+ }
+ return false;
+ }
+
+ static bool TryImportImage(Stream stream,
+ [MaybeNullWhen(false)] out ImportedImage image)
+ {
+ image = null!;
+ var success = ToBeNamed.TryImportImage(stream, out image!);
+ if (!success)
+ return false;
+
+ return true;
+ }
+
#if WPF
///
/// Gets a value indicating whether this image is JPEG.
@@ -1399,7 +1420,6 @@ internal XGraphics? AssociatedGraphics
XGraphics? _associatedGraphics;
#if CORE || GDI || WPF
- // ReSharper disable once InconsistentNaming
internal ImportedImage? _importedImage;
#endif
#if GDI
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XKnownColorTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XKnownColorTable.cs
index 08448b28..f8eb7df3 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XKnownColorTable.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XKnownColorTable.cs
@@ -186,6 +186,6 @@ static XKnownColorTable()
ColorTable[140] = 0xFF9ACD32; // YellowGreen
}
- internal static uint[] ColorTable = new uint[141];
+ internal static readonly uint[] ColorTable = new uint[141];
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs
index ef43ab38..ce88b043 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs
@@ -1,9 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
using System.ComponentModel;
-using PdfSharp.Internal;
+using PdfSharp.Internal.Threading;
#if GDI
using System.Drawing;
using System.Drawing.Drawing2D;
@@ -120,17 +119,17 @@ public XLinearGradientBrush(XRect rect, XColor color1, XColor color2, XLinearGra
//public XLinearGradientBrush(RectangleF rect, XColor color1, XColor color2, double angle, bool isAngleScaleable);
//public XLinearGradientBrush(RectangleF rect, XColor color1, XColor color2, double angle, bool isAngleScaleable);
- //private Blend _GetBlend();
- //private ColorBlend _GetInterpolationColors();
- //private XColor[] _GetLinearColors();
- //private RectangleF _GetRectangle();
- //private Matrix _GetTransform();
- //private WrapMode _GetWrapMode();
- //private void _SetBlend(Blend blend);
- //private void _SetInterpolationColors(ColorBlend blend);
- //private void _SetLinearColors(XColor color1, XColor color2);
- //private void _SetTransform(Matrix matrix);
- //private void _SetWrapMode(WrapMode wrapMode);
+ //Blend _GetBlend();
+ //ColorBlend _GetInterpolationColors();
+ //XColor[] _GetLinearColors();
+ //RectangleF _GetRectangle();
+ //Matrix _GetTransform();
+ //WrapMode _GetWrapMode();
+ //void _SetBlend(Blend blend);
+ //void _SetInterpolationColors(ColorBlend blend);
+ //void _SetLinearColors(XColor color1, XColor color2);
+ //void _SetTransform(Matrix matrix);
+ //void _SetWrapMode(WrapMode wrapMode);
//public override object Clone();
@@ -238,7 +237,7 @@ internal override System.Drawing.Brush RealizeGdiBrush()
GdiLinearGradientBrush brush;
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
if (_useRect)
{
brush = new GdiLinearGradientBrush(_rect.ToRectangleF(),
@@ -254,7 +253,7 @@ internal override System.Drawing.Brush RealizeGdiBrush()
brush.Transform = _matrix.ToGdiMatrix();
//brush.WrapMode = WrapMode.Clamp;
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
return brush;
}
#endif
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XMatrix.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XMatrix.cs
index f03a5f43..bf437487 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XMatrix.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XMatrix.cs
@@ -15,10 +15,8 @@ namespace PdfSharp.Drawing
/// Represents a 3-by-3 matrix that represents an affine 2D transformation.
///
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
- [Serializable,
- StructLayout(LayoutKind
- .Sequential)] //, TypeConverter(typeof(MatrixConverter)), ValueSerializer(typeof(MatrixValueSerializer))]
- public struct XMatrix : IFormattable
+ [Serializable, StructLayout(LayoutKind.Sequential)] //, TypeConverter(typeof(MatrixConverter)), ValueSerializer(typeof(MatrixValueSerializer))]
+ public struct XMatrix : IFormattable, IEquatable
{
[Flags]
internal enum XMatrixTypes
@@ -315,7 +313,7 @@ public void Scale(double scaleX, double scaleY, XMatrixOrder order)
[Obsolete("Use ScaleAppend or ScalePrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)]
// ReSharper disable InconsistentNaming
public void Scale(double scaleXY)
- // ReSharper restore InconsistentNaming
+ // ReSharper restore InconsistentNaming
{
throw new InvalidOperationException("Temporarily out of order.");
//Scale(scaleXY, scaleXY, XMatrixOrder.Prepend);
@@ -326,7 +324,7 @@ public void Scale(double scaleXY)
///
// ReSharper disable InconsistentNaming
public void ScaleAppend(double scaleXY)
- // ReSharper restore InconsistentNaming
+ // ReSharper restore InconsistentNaming
{
Scale(scaleXY, scaleXY, XMatrixOrder.Append);
}
@@ -336,7 +334,7 @@ public void ScaleAppend(double scaleXY)
///
// ReSharper disable InconsistentNaming
public void ScalePrepend(double scaleXY)
- // ReSharper restore InconsistentNaming
+ // ReSharper restore InconsistentNaming
{
Scale(scaleXY, scaleXY, XMatrixOrder.Prepend);
}
@@ -346,7 +344,7 @@ public void ScalePrepend(double scaleXY)
///
// ReSharper disable InconsistentNaming
public void Scale(double scaleXY, XMatrixOrder order)
- // ReSharper restore InconsistentNaming
+ // ReSharper restore InconsistentNaming
{
Scale(scaleXY, scaleXY, order);
}
@@ -492,7 +490,7 @@ public void RotateAt(double angle, XPoint point)
///
public void RotateAtAppend(double angle, XPoint point)
{
- RotateAt(angle, point, XMatrixOrder.Append);
+ RotateAt(angle * Const.Deg2Rad, point, XMatrixOrder.Append);
}
///
@@ -508,9 +506,9 @@ public void RotateAtPrepend(double angle, XPoint point)
///
public void RotateAt(double angle, XPoint point, XMatrixOrder order)
{
+ angle %= 360.0;
if (order == XMatrixOrder.Append)
{
- angle %= 360.0;
this *= CreateRotationRadians(angle * Const.Deg2Rad, point.X, point.Y);
//Translate(point.X, point.Y, order);
@@ -519,7 +517,6 @@ public void RotateAt(double angle, XPoint point, XMatrixOrder order)
}
else
{
- angle %= 360.0;
this = CreateRotationRadians(angle * Const.Deg2Rad, point.X, point.Y) * this;
}
@@ -803,9 +800,10 @@ public void Invert()
{
double determinant = Determinant;
if (DoubleUtil.IsZero(determinant))
- throw
- new InvalidOperationException(
- "NotInvertible"); //SR.Get(SRID.Transform_NotInvertible, new object[0]));
+ {
+ throw new InvalidOperationException(
+ "NotInvertible"); //SR.Get(SRID.Transform_NotInvertible, new object[0]));
+ }
switch (_type)
{
@@ -830,13 +828,13 @@ public void Invert()
return;
default:
- {
- double detInvers = 1.0 / determinant;
- SetMatrix(_m22 * detInvers, -_m12 * detInvers, -_m21 * detInvers, _m11 * detInvers,
- (_m21 * _offsetY - _offsetX * _m22) * detInvers,
- (_offsetX * _m12 - _m11 * _offsetY) * detInvers, XMatrixTypes.Unknown);
- break;
- }
+ {
+ double detInvers = 1.0 / determinant;
+ SetMatrix(_m22 * detInvers, -_m12 * detInvers, -_m21 * detInvers, _m11 * detInvers,
+ (_m21 * _offsetY - _offsetX * _m22) * detInvers,
+ (_offsetX * _m12 - _m11 * _offsetY) * detInvers, XMatrixTypes.Unknown);
+ break;
+ }
}
}
@@ -1225,7 +1223,7 @@ internal void MultiplyPoint(ref double x, ref double y)
y *= _m22;
return;
- case (XMatrixTypes.Scaling | XMatrixTypes.Translation):
+ case XMatrixTypes.Scaling | XMatrixTypes.Translation:
x *= _m11;
x += _offsetX;
y *= _m22;
@@ -1305,8 +1303,7 @@ static XMatrix CreateIdentity()
///
/// Sets the matrix.
///
- void SetMatrix(double m11, double m12, double m21, double m22, double offsetX, double offsetY,
- XMatrixTypes type)
+ void SetMatrix(double m11, double m12, double m21, double m22, double offsetX, double offsetY, XMatrixTypes type)
{
_m11 = m11;
_m12 = m12;
@@ -1360,8 +1357,8 @@ internal static class MatrixHelper
// Fast multiplication taking matrix type into account. Reflectored from WPF.
internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2)
{
- XMatrixTypes type1 = matrix1._type;
- XMatrixTypes type2 = matrix2._type;
+ var type1 = matrix1._type;
+ var type2 = matrix2._type;
if (type2 != XMatrixTypes.Identity)
{
if (type1 == XMatrixTypes.Identity)
@@ -1389,13 +1386,11 @@ internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2)
{
switch (((int)type1 << 4) | (int)type2)
{
- //case 0x22:
case ((int)XMatrixTypes.Scaling << 4) | (int)XMatrixTypes.Scaling:
matrix1._m11 *= matrix2._m11;
matrix1._m22 *= matrix2._m22;
return;
- //case 0x23:
case ((int)XMatrixTypes.Scaling << 4) | (int)XMatrixTypes.Translation |
(int)XMatrixTypes.Scaling:
@@ -1406,7 +1401,6 @@ internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2)
matrix1._type = XMatrixTypes.Scaling | XMatrixTypes.Translation;
return;
- //case 0x32:
case (((int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling) << 4) |
(int)XMatrixTypes.Scaling:
matrix1._m11 *= matrix2._m11;
@@ -1415,7 +1409,6 @@ internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2)
matrix1._offsetY *= matrix2._m22;
return;
- //case 0x33:
case (((int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling) << 4) |
(int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling:
matrix1._m11 *= matrix2._m11;
@@ -1423,28 +1416,26 @@ internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2)
matrix1._offsetX = matrix2._m11 * matrix1._offsetX + matrix2._offsetX;
matrix1._offsetY = matrix2._m22 * matrix1._offsetY + matrix2._offsetY;
return;
+
- //case 0x24:
case ((int)XMatrixTypes.Scaling << 4) | (int)XMatrixTypes.Unknown:
- //case 0x34:
+
case (((int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling) << 4) |
(int)XMatrixTypes.Unknown:
- //case 0x42:
+
case ((int)XMatrixTypes.Unknown << 4) | (int)XMatrixTypes.Scaling:
- //case 0x43:
+
case ((int)XMatrixTypes.Unknown << 4) | (int)XMatrixTypes.Translation |
(int)XMatrixTypes.Scaling:
- //case 0x44:
+
case ((int)XMatrixTypes.Unknown << 4) | (int)XMatrixTypes.Unknown:
matrix1 = new XMatrix(
matrix1._m11 * matrix2._m11 + matrix1._m12 * matrix2._m21,
matrix1._m11 * matrix2._m12 + matrix1._m12 * matrix2._m22,
matrix1._m21 * matrix2._m11 + matrix1._m22 * matrix2._m21,
matrix1._m21 * matrix2._m12 + matrix1._m22 * matrix2._m22,
- matrix1._offsetX * matrix2._m11 + matrix1._offsetY * matrix2._m21 +
- matrix2._offsetX,
- matrix1._offsetX * matrix2._m12 + matrix1._offsetY * matrix2._m22 +
- matrix2._offsetY);
+ matrix1._offsetX * matrix2._m11 + matrix1._offsetY * matrix2._m21 + matrix2._offsetX,
+ matrix1._offsetX * matrix2._m12 + matrix1._offsetY * matrix2._m22 + matrix2._offsetY);
return;
}
}
@@ -1460,8 +1451,8 @@ internal static void PrependOffset(ref XMatrix matrix, double offsetX, double of
}
else
{
- matrix._offsetX += (matrix._m11 * offsetX) + (matrix._m21 * offsetY);
- matrix._offsetY += (matrix._m12 * offsetX) + (matrix._m22 * offsetY);
+ matrix._offsetX += matrix._m11 * offsetX + matrix._m21 * offsetY;
+ matrix._offsetY += matrix._m12 * offsetX + matrix._m22 * offsetY;
if (matrix._type != XMatrixTypes.Unknown)
matrix._type |= XMatrixTypes.Translation;
}
@@ -1521,7 +1512,7 @@ internal static void TransformRect(ref XRect rect, ref XMatrix matrix)
/// The debugger display.
// ReSharper disable UnusedMember.Local
string DebuggerDisplay
- // ReSharper restore UnusedMember.Local
+ // ReSharper restore UnusedMember.Local
{
get
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs
index a3f3335b..d55d0fc8 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs
@@ -19,7 +19,7 @@ namespace PdfSharp.Drawing
/// PDF document. XPdfForm objects are used like images to draw an existing PDF page of an external
/// document in the current document. XPdfForm objects can only be placed in PDF documents. If you try
/// to draw them using a XGraphics based on an GDI+ context no action is taken if no placeholder image
- /// is specified. Otherwise, the place holder is drawn.
+ /// is specified. Otherwise, the placeholder is drawn.
///
public class XPdfForm : XForm
{
@@ -90,7 +90,7 @@ void Initialize()
///
internal override void Finish()
{
- if (_formState == FormState.NotATemplate || _formState == FormState.Finished)
+ if (_formState is FormState.NotATemplate or FormState.Finished)
return;
base.Finish();
@@ -110,10 +110,10 @@ internal override void Finish()
// if (_document.Options.CompressContentStreams)
// {
// _pdfForm.Stream.Value = Filtering.FlateDecode.Encode(pdfForm.Stream.Value);
- // _pdfForm.Elements["/Filter"] = new PdfName("/FlateDecode");
+ // _pdfForm.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
// }
// int length = _pdfForm.Stream.Length;
- // _pdfForm.Elements.SetInteger("/Length", length);
+ // _pdfForm.Elements.SetInteger(PdfStream.Keys.Length, length);
//}
}
@@ -267,14 +267,24 @@ public override XSize Size
///
public override XMatrix Transform
{
- get => _transform;
+ //get => _transform;
+ //set
+ //{
+ // if (_transform != value)
+ // {
+ // // Discard PdfFromXObject when Transform changed.
+ // _pdfForm = null;
+ // _transform = value;
+ // }
+ //}
+ get => base.Transform;
set
{
- if (_transform != value)
+ if (Transform != value)
{
// Discard PdfFromXObject when Transform changed.
_pdfForm = null;
- _transform = value;
+ Transform = value;
}
}
}
@@ -339,7 +349,7 @@ internal PdfDocument ExternalDocument
///
public static string ExtractPageNumber(string path, out int pageNumber)
{
- // Note: duplicated in class ImageHelper.
+ // This code is duplicated in class ImageHelper.
if (path == null)
throw new ArgumentNullException(nameof(path));
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs
index fd950c21..c1c9f4bd 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs
@@ -1,8 +1,9 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-#if GDI
using PdfSharp.Internal;
+using PdfSharp.Internal.Threading;
+#if GDI
using System.Drawing.Drawing2D;
using GdiPen = System.Drawing.Pen;
#endif
@@ -241,7 +242,7 @@ public static implicit operator XPen(Pen pen)
XPen xpen;
try
{
- Lock.EnterGdiPlus();
+ Locks.EnterGdiPlus();
xpen = pen.PenType switch
{
PenType.SolidColor => new(pen.Color, pen.Width)
@@ -250,7 +251,7 @@ public static implicit operator XPen(Pen pen)
DashStyle = (XDashStyle)pen.DashStyle,
_miterLimit = pen.MiterLimit
},
- _ => throw new NotImplementedException("Pen type not supported by PDFsharp.")
+ _ => throw new NotSupportedException("Pen type not supported by PDFsharp.")
};
// Custom dash style, fix by drice2@ageone.de.
if (pen.DashStyle == System.Drawing.Drawing2D.DashStyle.Custom)
@@ -263,7 +264,7 @@ public static implicit operator XPen(Pen pen)
xpen._dashOffset = pen.DashOffset;
}
}
- finally { Lock.ExitGdiPlus(); }
+ finally { Locks.ExitGdiPlus(); }
return xpen;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPoint.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPoint.cs
index b80d06a6..aba5b4fc 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPoint.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPoint.cs
@@ -26,7 +26,7 @@ namespace PdfSharp.Drawing
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
[Serializable]
[StructLayout(LayoutKind.Sequential)] // TypeConverter(typeof(PointConverter)), ValueSerializer(typeof(PointValueSerializer))]
- public struct XPoint : IFormattable
+ public struct XPoint : IFormattable, IEquatable
{
///
/// Initializes a new instance of the XPoint class with the specified coordinates.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRect.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRect.cs
index 10f1641a..bd748006 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRect.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRect.cs
@@ -26,7 +26,7 @@ namespace PdfSharp.Drawing
///
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
[Serializable, StructLayout(LayoutKind.Sequential)] // , ValueSerializer(typeof(RectValueSerializer)), TypeConverter(typeof(RectConverter))]
- public struct XRect : IFormattable
+ public struct XRect : IFormattable, IEquatable
{
///
/// Initializes a new instance of the XRect class.
@@ -141,7 +141,7 @@ public static XRect FromLTRB(double left, double top, double right, double botto
///
/// Determines whether the two rectangles are equal.
///
- // ReSharper disable CompareOfFloatsByEqualityOperator
+ // ReSharper disable CompareOfFloatsByEqualityOperator
public static bool operator ==(XRect rect1, XRect rect2) =>
rect1.X == rect2.X && rect1.Y == rect2.Y && rect1.Width == rect2.Width && rect1.Height == rect2.Height;
// ReSharper restore CompareOfFloatsByEqualityOperator
@@ -363,7 +363,7 @@ public double Right
get
{
if (IsEmpty)
- return double.NegativeInfinity;
+ return Double.NegativeInfinity;
return _x + _width;
}
}
@@ -376,7 +376,7 @@ public double Bottom
get
{
if (IsEmpty)
- return double.NegativeInfinity;
+ return Double.NegativeInfinity;
return _y + _height;
}
}
@@ -660,7 +660,7 @@ public void Scale(double scaleX, double scaleY)
///
/// Converts this instance to a System.Drawing.RectangleF.
///
- public RectangleF ToRectangleF()
+ public RectangleF ToRectangleF()
=> new((float)_x, (float)_y, (float)_width, (float)_height);
#endif
@@ -668,13 +668,13 @@ public RectangleF ToRectangleF()
///
/// Performs an implicit conversion from a System.Drawing.Rectangle to an XRect.
///
- public static implicit operator XRect(Rectangle rect)
+ public static implicit operator XRect(Rectangle rect)
=> new(rect.X, rect.Y, rect.Width, rect.Height);
///
/// Performs an implicit conversion from a System.Drawing.RectangleF to an XRect.
///
- public static implicit operator XRect(RectangleF rect)
+ public static implicit operator XRect(RectangleF rect)
=> new(rect.X, rect.Y, rect.Width, rect.Height);
#endif
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XSize.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XSize.cs
index 86d851eb..f970a2b9 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XSize.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XSize.cs
@@ -267,7 +267,6 @@ public double Height
///
/// Gets the DebuggerDisplayAttribute text.
///
- /// The debugger display.
// ReSharper disable UnusedMember.Local
string DebuggerDisplay
// ReSharper restore UnusedMember.Local
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XStringFormat.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XStringFormat.cs
index 0addd457..4e1292aa 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XStringFormat.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XStringFormat.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using System;
@@ -161,7 +161,7 @@ internal StringFormat RealizeGdiStringFormat()
//_stringFormat.FormatFlags = (StringFormatFlags)_formatFlags;
// Bugfix: Set MeasureTrailingSpaces to get the correct width with Graphics.MeasureString().
- // Before, MeasureString() didnt include blanks in width calculation, which could result in text overflowing table or page border before wrapping. $MaOs
+ // Before, MeasureString() didn’t include blanks in width calculation, which could result in text overflowing table or page border before wrapping. $MaOs
_stringFormat.FormatFlags = _stringFormat.FormatFlags | StringFormatFlags.MeasureTrailingSpaces;
}
return _stringFormat;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XTypeface.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XTypeface.cs
index d1f799aa..6b4c8fa1 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XTypeface.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XTypeface.cs
@@ -17,7 +17,7 @@ namespace PdfSharp.Drawing
/// Represents a combination of XFontFamily, XFontWeight, XFontStyleEx, and XFontStretch.
///
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
- public class XTypeface // Note: In English, it’s spelled 'typeface', but 'font face'.
+ public class XTypeface // Note that in English it’s spelled 'typeface', but 'font face'.
{
///
/// Initializes a new instance of the class.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XVector.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XVector.cs
index c3f94ebb..457ae065 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XVector.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XVector.cs
@@ -19,7 +19,7 @@ namespace PdfSharp.Drawing
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
- public struct XVector : IFormattable
+ public struct XVector : IFormattable, IEquatable
{
///
/// Initializes a new instance of the struct.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XFontStyleEx.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XFontStyleEx.cs
index dd18c517..8de2e228 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XFontStyleEx.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XFontStyleEx.cs
@@ -10,8 +10,6 @@ namespace PdfSharp.Drawing
[Flags]
public enum XFontStyleEx // Same values as System.Drawing.FontStyle.
{
- // Will be renamed to XGdiFontStyle or XWinFontStyle.
-
///
/// Normal text.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XGraphicsUnit.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XGraphicsUnit.cs
index 5c6a9a5a..4f2891b6 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XGraphicsUnit.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XGraphicsUnit.cs
@@ -8,6 +8,8 @@ namespace PdfSharp.Drawing
///
public enum XGraphicsUnit // NOT the same values as System.Drawing.GraphicsUnit
{
+ // Values are identical to PdfSharp.Graphics.GraphicsUnit.
+
///
/// Specifies a printer’s point (1/72 inch) as the unit of measure.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XSmoothingMode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XSmoothingMode.cs
index beac608f..cddaf507 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XSmoothingMode.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XSmoothingMode.cs
@@ -10,7 +10,7 @@ namespace PdfSharp.Drawing
/// and the edges of filled areas.
///
[Flags]
- public enum XSmoothingMode // same values as System.Drawing.Drawing2D.SmoothingMode
+ public enum XSmoothingMode // Same values as System.Drawing.Drawing2D.SmoothingMode
{
// Not used in PDF rendering process.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/DocumentEvents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/DocumentEvents.cs
index 6ba09fbe..0951d676 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Events/DocumentEvents.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/DocumentEvents.cs
@@ -3,117 +3,64 @@
using PdfSharp.Drawing;
using PdfSharp.Pdf;
+using PdfSharp.Pdf.Metadata;
+
+// TODO REMOVE
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Events
{
- ///
- /// The event type of PageEvent.
- ///
- public enum PageEventType
- {
- ///
- /// A new page was created.
- ///
- Created,
-
- ///
- /// A page was moved.
- ///
- Moved,
-
- ///
- /// A page was imported from another document.
- ///
- Imported,
-
- ///
- /// A page was removed.
- ///
- Removed
- }
-
- ///
+ /// // TODO
/// EventArgs for changes in the PdfPages of a document.
///
- public class PageEventArgs(PdfObject source) : PdfSharpEventArgs(source)
+ public class DocumentMetadataEventArgs(PdfDocument source) : PdfSharpEventArgs(source)
{
- ///
- /// Gets or sets the affected page.
- ///
- public PdfPage Page { get; set; } = default!;
-
- ///
- /// Gets or sets the page index of the affected page.
- ///
- public int PageIndex { get; set; }
+ public required PdfMetadata Metadata { get; init; }
- ///
- /// The event type of PageEvent.
- ///
- public PageEventType EventType { get; internal set; }
+ public required DocumentMetadataInfo Info { get; init; }
}
- ///
- /// EventHandler for OnPageAdded and OnPageRemoved.
+ /// // TODO
+ ///
///
/// The sender of the event.
- /// The PageEventArgs of the event.
- public delegate void PageAddedOrRemovedEventHandler(object sender, PageEventArgs e);
+ /// The XmlMetadataEventArgs of the event.
+ public delegate void DocumentMetadataEventHandler(object sender, DocumentMetadataEventArgs e);
+}
+namespace PdfSharp.Events // MaOs4StLa Review.
+{
///
- /// The action type of PageGraphicsEvent.
+ /// EventArgs for a document.
///
- public enum PageGraphicsActionType
- {
- ///
- /// The XGraphics object for the page was created.
- ///
- GraphicsCreated = 1,
-
- ///
- /// DrawString() was called on the page’s XGraphics object.
- ///
- DrawString,
-
- ///
- /// Another method drawing content was called on the page’s XGraphics object.
- ///
- Draw
- }
+ public class DocumentEventArgs(PdfDocument source) : PdfSharpEventArgs(source)
+ { }
+ public delegate void DocumentEventHandler(object sender, DocumentEventArgs e);
+}
+
+namespace PdfSharp.Events
+{
///
- /// EventArgs for actions on a page’s XGraphics object.
+ /// A class encapsulating all events of a PdfDocument.
///
- public class PageGraphicsEventArgs(PdfObject source) : PdfSharpEventArgs(source)
+ public class DocumentEvents
{
///
- /// Gets the page that causes the event.
+ /// An event raised if a document gets disposed.
///
- public PdfPage Page { get; internal set; } = default!;
+ /// The sender of the event.
+ /// The DocumentEventArgs of the event.
+ public void OnDisposed(object sender, DocumentEventArgs args) // MaOs4StLa Review.
+ {
+ Disposed?.Invoke(sender, args);
+ }
///
- /// Gets the created XGraphics object.
+ /// EventHandler for OnDisposed.
///
- public XGraphics Graphics { get; internal set; } = default!;
+ public event DocumentEventHandler? Disposed; // MaOs4StLa Review.
- ///
- /// The action type of PageGraphicsEvent.
- ///
- public PageGraphicsActionType ActionType { get; internal set; }
- }
-
- ///
- /// EventHandler for OnPageGraphicsAction.
- ///
- /// The sender of the event.
- /// The PageGraphicsEventArgs of the event.
- public delegate void PageGraphicsEventHandler(object sender, PageGraphicsEventArgs e);
-
- ///
- /// A class encapsulating all events of a PdfDocument.
- ///
- public class DocumentEvents
- {
///
/// An event raised if a page was added.
///
@@ -173,5 +120,23 @@ public void OnPageGraphicsAction(object sender, PageGraphicsEventArgs args)
/// EventHandler for OnPageGraphicsAction.
///
public event PageGraphicsEventHandler? PageGraphicsAction;
+ ///
+ /// An event raised if something is drawn on a page’s XGraphics object. TODO
+ ///
+ /// The sender of the event.
+ /// The PageGraphicsEventArgs of the event.
+ public void OnCreateDocumentMetadata(object sender, DocumentMetadataEventArgs args)
+ {
+ if (CreateDocumentMetadata == null)
+ throw new InvalidOperationException("The document event CreateDocumentMetadata is not set and cannot be invoked. "+
+ "You must provide a delegate that handle the CreateDocumentMetadata event.");
+
+ CreateDocumentMetadata.Invoke(sender, args);
+ }
+
+ ///
+ /// EventHandler for OnCreateDocumentMetadata.
+ ///
+ public event DocumentMetadataEventHandler? CreateDocumentMetadata;
}
}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageEvents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageEvents.cs
new file mode 100644
index 00000000..ecac0d98
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageEvents.cs
@@ -0,0 +1,62 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf;
+
+namespace PdfSharp.Events // #FILE PageEvents.cs
+{
+ ///
+ /// The event type of PageEvent.
+ ///
+ public enum PageEventType
+ {
+ ///
+ /// A new page was created.
+ ///
+ Created,
+
+ ///
+ /// A page was moved.
+ ///
+ Moved,
+
+ ///
+ /// A page was imported from another document.
+ ///
+ Imported,
+
+ ///
+ /// A page was removed.
+ ///
+ Removed
+ }
+
+ ///
+ /// EventArgs for changes in the PdfPages of a document.
+ ///
+ //public class PageEventArgs(PdfObject source) : PdfSharpEventArgs(source)
+ public class PageEventArgs(PdfDocument source) : PdfSharpEventArgs(source)
+ {
+ ///
+ /// Gets or sets the affected page.
+ ///
+ public PdfPage Page { get; set; } = null!;
+
+ ///
+ /// Gets or sets the page index of the affected page.
+ ///
+ public int PageIndex { get; set; }
+
+ ///
+ /// The event type of PageEvent.
+ ///
+ public PageEventType EventType { get; internal set; }
+ }
+
+ ///
+ /// EventHandler for OnPageAdded and OnPageRemoved.
+ ///
+ /// The sender of the event.
+ /// The PageEventArgs of the event.
+ public delegate void PageAddedOrRemovedEventHandler(object sender, PageEventArgs e);
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageGraphicsEvents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageGraphicsEvents.cs
new file mode 100644
index 00000000..7818566e
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageGraphicsEvents.cs
@@ -0,0 +1,57 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Drawing;
+using PdfSharp.Pdf;
+
+namespace PdfSharp.Events
+{
+ ///
+ /// The action type of PageGraphicsEvent.
+ ///
+ public enum PageGraphicsActionType
+ {
+ ///
+ /// The XGraphics object for the page was created.
+ ///
+ GraphicsCreated = 1,
+
+ ///
+ /// DrawString() was called on the page’s XGraphics object.
+ ///
+ DrawString,
+
+ ///
+ /// Another method drawing content was called on the page’s XGraphics object.
+ ///
+ Draw
+ }
+
+ ///
+ /// EventArgs for actions on a page’s XGraphics object.
+ ///
+ public class PageGraphicsEventArgs(PdfDocument source) : PdfSharpEventArgs(source)
+ {
+ ///
+ /// Gets the page that causes the event.
+ ///
+ public PdfPage Page { get; internal init; } = null!;
+
+ ///
+ /// Gets the created XGraphics object.
+ ///
+ public XGraphics Graphics { get; internal init; } = null!;
+
+ ///
+ /// The action type of PageGraphicsEvent.
+ ///
+ public PageGraphicsActionType ActionType { get; internal init; }
+ }
+
+ ///
+ /// EventHandler for OnPageGraphicsAction.
+ ///
+ /// The sender of the event.
+ /// The PageGraphicsEventArgs of the event.
+ public delegate void PageGraphicsEventHandler(object sender, PageGraphicsEventArgs e);
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/PdfSharpEventArgs.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PdfSharpEventArgs.cs
index c51203b0..e1406a22 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Events/PdfSharpEventArgs.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PdfSharpEventArgs.cs
@@ -3,16 +3,18 @@
using PdfSharp.Pdf;
+// v7.0.0 Ready
+
namespace PdfSharp.Events
{
///
/// Base class for EventArgs in PDFsharp.
///
- public abstract class PdfSharpEventArgs(PdfObject source) : EventArgs
+ public abstract class PdfSharpEventArgs(PdfDocument source) : EventArgs
{
///
- /// The source of the event.
+ /// The source PDF document of the event.
///
- public PdfObject Source { get; set; } = source;
+ public PdfDocument Source { get; set; } = source;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/RenderEvents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/RenderEvents.cs
index 82a4ffbd..12286d31 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Events/RenderEvents.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/RenderEvents.cs
@@ -1,8 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal.OpenType;
using PdfSharp.Drawing;
-using PdfSharp.Fonts;
using PdfSharp.Pdf;
namespace PdfSharp.Events
@@ -10,7 +10,8 @@ namespace PdfSharp.Events
///
/// EventArgs for PrepareTextEvent.
///
- public class PrepareTextEventArgs(PdfObject source, XFont font, string text) : PdfSharpEventArgs(source)
+ public class PrepareTextEventArgs(PdfDocument source, XFont font, string text)
+ : PdfSharpEventArgs(source)
{
///
/// Gets the font used to draw the text.
@@ -35,7 +36,8 @@ public class PrepareTextEventArgs(PdfObject source, XFont font, string text) : P
///
/// EventArgs for RenderTextEvent.
///
- public class RenderTextEventArgs(PdfObject source, XFont font, CodePointGlyphIndexPair[] codePointGlyphIndexPair) : PdfSharpEventArgs(source)
+ public class RenderTextEventArgs(PdfDocument source, XFont font, CodePointGlyphIndexPair[] codePointGlyphIndexPair)
+ : PdfSharpEventArgs(source)
{
///
/// Gets or sets a value indicating whether the determination of the glyph identifiers must be reevaluated.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontHelper.cs
index 1114e3ba..0432136e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontHelper.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontHelper.cs
@@ -1,6 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal.OpenType;
+using PdfSharp.Drawing;
#if GDI
using PdfSharp.Logging;
using GdiFontFamily = System.Drawing.FontFamily;
@@ -22,15 +24,6 @@
using Windows.UI.Text;
using Windows.UI.Xaml.Media;
#endif
-//using System.Drawing;
-//using Microsoft.Extensions.Logging;
-//using PdfSharp.Events;
-//using PdfSharp.Fonts;
-using Microsoft.Extensions.Logging;
-using PdfSharp.Drawing;
-using PdfSharp.Fonts;
-//using PdfSharp.Fonts.OpenType;
-//using PdfSharp.Logging;
namespace PdfSharp.Fonts.Internal
{
@@ -39,109 +32,6 @@ namespace PdfSharp.Fonts.Internal
///
static class FontHelper
{
-#if true_ // #DELETE 25-12-31
- ///
- /// Measure string directly from font data.
- ///
- public static XSize MeasureString(string text, XFont font)
- {
- Debug.Assert(false, "Use the new version with code run parameter.");
-
- if (String.IsNullOrEmpty(text))
- return new(); // not XSize.Empty !
-
- var cp = UnicodeHelper.Utf32FromString2(text);
- var codePoints = font.OpenTypeDescriptor.GlyphIndicessFromCodepoints(cp);
- //var codeRun = new CharacterCodeRun(codePoints);
-
- //// Invoke RenderEvent.
- //var args = new RenderCodeRunEventArgs(Owner)
- //{
- // Font = font,
- // CodeRun = codeRun
- //};
- //Owner.RenderEvents.OnRenderCodeRun(this, args);
- //codeRun = args.CodeRun;
- //codePoints = codeRun.Items;
-
- return MeasureString(codePoints, font);
-#if true_ // Keep until 2025-12-31 for reference
- var size = new XSize();
- var descriptor = FontDescriptorCache.GetOrCreateDescriptorFor(font) as OpenTypeDescriptor;
- if (descriptor != null)
- {
- // Height is the sum of ascender and descender.
- size.Height = (descriptor.Ascender + descriptor.Descender) * font.Size / font.UnitsPerEm;
- Debug.Assert(descriptor.Ascender > 0, "Ascender must be greater than 0.");
-
- bool isSymbolFont = descriptor.IsSymbolFont;
- int length = text.Length;
- int width = 0;
- for (int idx = 0; idx < length; idx++)
- {
- char ch = text[idx];
- // H/A/C/K: Unclear what to do here.
- if (ch < 32)
- continue;
-
- if (Char.IsLowSurrogate(ch))
- {
- // We only come here when the text contains a low surrogate not preceded by a high surrogate.
- // This is an error in the UTF-32 text and therefore ignored.
- PdfSharpLogHost.FontManagementLogger.LogWarning("Unexpected low surrogate found: 0x{Char:X2}", ch);
- continue;
- }
-
- int glyphIndex;
- if (isSymbolFont)
- {
- ch = descriptor.RemapSymbolChar(ch);
- glyphIndex = descriptor.BmpCodepointToGlyphIndex(ch);
- }
- else if (Char.IsHighSurrogate(ch))
- {
- // UTF16 surrogate pair expected.
- if (++idx < length)
- {
- var ch2 = text[idx];
- if (Char.IsLowSurrogate(ch2) is false)
- {
- PdfSharpLogHost.FontManagementLogger.LogWarning("High surrogate 0x{Char:X2} not followed by low surrogate.", ch);
- continue;
- }
- glyphIndex = descriptor.SurrogatePairToGlyphIndex(ch, ch2);
- }
- else
- {
- PdfSharpLogHost.FontManagementLogger.LogWarning("High surrogate 0x{Char:X2} found at end of string.", ch);
- continue;
- }
- }
- else
- {
- // BMP character.
- glyphIndex = descriptor.BmpCodepointToGlyphIndex(ch);
- }
- width += descriptor.GlyphIDToWidth(glyphIndex);
- }
- size.Width = width * font.Size / descriptor.UnitsPerEm;
-
- // Adjust bold simulation.
- if ((font.GlyphTypeface.StyleSimulations & XStyleSimulations.BoldSimulation) == XStyleSimulations.BoldSimulation)
- {
- // Add 2% of the em-size for each character.
- // Unsure how to deal with white space. Currently count as regular character.
- size.Width += length * font.Size * Const.BoldEmphasis;
- }
- }
- // BUG_OLD: Is it correct to return an empty size if we have no descriptor?
- Debug.Assert(descriptor != null, "No OpenTypeDescriptor.");
-
- return size;
-#endif
- }
-#endif
-
///
/// Measure string directly from font data.
/// This function expects that the code run is ready to be measured.
@@ -174,7 +64,7 @@ public static XSize MeasureString(CodePointGlyphIndexPair[] codeRun, XFont font)
if ((font.GlyphTypeface.StyleSimulations & XStyleSimulations.BoldSimulation) == XStyleSimulations.BoldSimulation)
{
// Add 2% of the em-size for each character.
- // Unsure how to deal with white space. Currently count as regular character.
+ // Unsure how to deal with white-space. Currently count as regular character.
size.Width += length * font.Size * Const.BoldEmphasis;
}
return size;
@@ -338,42 +228,6 @@ public static bool IsStyleAvailable(XFontFamily family, XFontStyleEx style)
return false;
}
#endif
-
- ///
- /// Calculates an Adler32 checksum combined with the buffer length
- /// in a 64-bit unsigned integer.
- ///
- public static ulong CalcChecksum(byte[] buffer)
- {
- if (buffer == null)
- throw new ArgumentNullException(nameof(buffer));
-
- const uint prime = 65521; // largest prime smaller than 65536
- uint s1 = 0;
- uint s2 = 0;
- int length = buffer.Length;
- int offset = 0;
- while (length > 0)
- {
- int n = 3800;
- if (n > length)
- n = length;
- length -= n;
- while (--n >= 0)
- {
- s1 += buffer[offset++];
- s2 += s1;
- }
- s1 %= prime;
- s2 %= prime;
- }
- //return ((ulong)((ulong)(((ulong)s2 << 16) | (ulong)s1)) << 32) | (ulong)buffer.Length;
- ulong ul1 = ((ulong)s2 << 16) | s1;
- //ul1 |= s1;
- uint ui2 = (uint)buffer.Length;
- return (ul1 << 32) | ui2;
- }
-
public static XFontStyleEx CreateStyle(bool isBold, bool isItalic)
=> (isBold ? XFontStyleEx.Bold : 0) | (isItalic ? XFontStyleEx.Italic : 0);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontResolvingOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontResolvingOptions.cs
index f516f6bf..abde8ae8 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontResolvingOptions.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontResolvingOptions.cs
@@ -1,13 +1,6 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-#if GDI
-using System.Drawing;
-using System.Drawing.Text;
-#endif
-#if WPF
-using System.Windows.Media;
-#endif
using PdfSharp.Drawing;
namespace PdfSharp.Fonts.Internal
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs-DELETE b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs-DELETE
new file mode 100644
index 00000000..583889b5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs-DELETE
@@ -0,0 +1,140 @@
+// DELETE
+
+////// PDFsharp - A .NET library for processing PDF
+////// See the LICENSE file in the solution root for more information.
+
+////using Microsoft.Extensions.Logging;
+////using PdfSharp.Internal.OpenType;
+////using PdfSharp.Logging;
+////using PdfSharp.Internal;
+
+////namespace PdfSharp.Fonts.Internal
+////{
+////#warning remove from here
+//// ///
+//// /// A bunch of internal functions to handle Unicode.
+//// ///
+//// static class UnicodeHelper
+//// {
+//// internal const char HighSurrogateStart = '\uD800';
+//// internal const char HighSurrogateEnd = '\uDBFF';
+//// internal const char LowSurrogateStart = '\uDC00';
+//// internal const char LowSurrogateEnd = '\uDFFF';
+//// internal const int HighSurrogateRange = 0x3FF;
+//// internal const int UnicodePlane01Start = 0x1_0000;
+
+//// ///
+//// /// Converts a UTF-16 string into an array of Unicode code points.
+//// ///
+//// /// The string to be converted.
+//// /// if set to true [coerce ANSI].
+//// /// The non ANSI.
+//// public static int[] Utf32FromString(string s, bool coerceAnsi = false, byte nonAnsi = (byte)'?') // #NFM docu, design, ...
+//// {
+//// if (String.IsNullOrEmpty(s))
+//// return [];
+
+//// int length = s.Length;
+//// var result = new int[length];
+//// int iRes = 0;
+
+//// for (int idx = 0; idx < length; idx++)
+//// {
+//// ref var current = ref result[iRes++];
+
+//// var ch = s[idx];
+//// current = ch;
+//// int high10Bits = ch - HighSurrogateStart;
+//// if ((uint)high10Bits <= HighSurrogateRange)
+//// {
+//// // Case: ch is a high surrogate.
+//// idx++;
+//// if ((uint)idx < (uint)s.Length)
+//// {
+//// var ch2 = s[idx];
+//// int low10Bits = ch2 - LowSurrogateStart;
+//// if ((uint)low10Bits <= HighSurrogateRange)
+//// {
+//// // Case: ch2 is a low surrogate.
+
+//// // Combine high and low surrogate code points into UTF-32 Unicode code point.
+//// current = (high10Bits << 10) + low10Bits + UnicodePlane01Start;
+//// }
+//// else
+//// {
+//// // Case: Unpaired high surrogate.
+//// LogHost.Logger./*FontManagementLogger.*/LogDebug("High surrogate 0x{Char:X2} not followed by a low surrogate.", ch);
+//// idx--;
+//// continue;
+//// }
+//// }
+//// else
+//// {
+//// // Case: High surrogate at string end.
+//// LogHost.Logger./*FontManagementLogger.*/LogDebug("High surrogate 0x{Char:X2} found at end of string.", ch);
+//// break;
+//// }
+//// }
+//// else
+//// {
+//// // Case: ch is in BMP range.
+
+//// //if (ch - LOW_SURROGATE_START <= HIGH_SURROGATE_RANGE)
+//// if ((uint)ch - LowSurrogateStart <= HighSurrogateRange)
+//// {
+//// // Case: unpaired low surrogate.
+//// // We only come here when the text contains a low surrogate not preceded by a high surrogate.
+//// // This is an error in the UTF-32 text.
+//// LogHost.Logger./*FontManagementLogger.*/LogDebug("Unexpected low surrogate found: 0x{Char:X2}", ch);
+//// continue;
+//// }
+
+//// if (coerceAnsi && !AnsiEncoding.IsAnsi(ch))
+//// current = nonAnsi;
+//// }
+//// }
+//// if (iRes < length)
+//// Array.Resize(ref result, iRes);
+//// return result;
+//// }
+
+//// ///
+//// /// Converts a UTF-16 string into an array of code points of a symbol font.
+//// ///
+//// public static int[] SymbolCodePointsFromString(string s, OpenTypeDescriptor openTypeDescriptor)
+//// {
+//// if (String.IsNullOrEmpty(s))
+//// return [];
+
+//// int length = s.Length;
+//// var result = new int[length];
+
+//// for (int idx = 0; idx < length; idx++)
+//// result[idx] = openTypeDescriptor.RemapSymbolChar(s[idx]);
+
+//// return result;
+//// }
+
+//// ///
+//// /// Convert a surrogate pair to UTF-32 code point.
+//// /// Similar to Char.ConvertToUtf32 but never throws an error.
+//// /// Instead, returns 0 if one of the surrogates are invalid.
+//// ///
+//// /// The high surrogate.
+//// /// The low surrogate.
+//// public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
+//// {
+//// uint highSurrogateOffset = (uint)highSurrogate - UnicodeHelper.HighSurrogateStart;
+//// uint lowSurrogateOffset = (uint)lowSurrogate - UnicodeHelper.LowSurrogateStart;
+
+//// // If surrogates not in range return 0.
+//// // The cool code using underflow effect to check two ranges with one comparison comes from the .NET source code.
+//// if ((highSurrogateOffset | lowSurrogateOffset) > UnicodeHelper.HighSurrogateRange)
+//// return 0;
+//// // Convert to code point.
+//// return ((int)highSurrogateOffset << 10) + (lowSurrogate - UnicodeHelper.LowSurrogateStart) + UnicodePlane01Start;
+//// }
+
+//// internal static bool IsInRange(char c, char min, char max) => (uint)(c - min) <= (uint)(max - min);
+//// }
+////}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs
deleted file mode 100644
index c481dc50..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs
+++ /dev/null
@@ -1,203 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-#if GDI
-using System.Drawing;
-using System.Drawing.Drawing2D;
-#endif
-#if WPF
-using System.Windows;
-using System.Windows.Media;
-#endif
-using PdfSharp.Pdf.Internal;
-using PdfSharp.Fonts;
-using PdfSharp.Drawing;
-using PdfSharp.Internal;
-
-#pragma warning disable 0649
-
-namespace PdfSharp.Fonts.OpenType
-{
- ///
- /// Base class for all font descriptors.
- /// Currently only OpenTypeDescriptor is derived from this base class.
- ///
- class FontDescriptor
- {
- protected FontDescriptor(string key)
- {
- Key = key;
- }
-
- public string Key { get; }
-
- ///
- ///
- ///
- public string FontName3 { get; init; } = default!; // #NFM check format of this name. It is the name of the XFont or the name of XGlyphTypeface.
-
- ///
- ///
- ///
- public string Weight
- {
- get;
- private set;
- // BUG_OLD: never set
- } = default!;
-
- ///
- /// Gets a value indicating whether this instance belongs to a bold font.
- ///
- public virtual bool IsBoldFace => false;
-
- ///
- ///
- ///
- public float ItalicAngle { get; protected set; }
-
- ///
- /// Gets a value indicating whether this instance belongs to an italic font.
- ///
- public virtual bool IsItalicFace => false;
-
- ///
- ///
- ///
- public int XMin { get; protected set; }
-
- ///
- ///
- ///
- public int YMin { get; protected set; }
-
- ///
- ///
- ///
- public int XMax { get; protected set; }
-
- ///
- ///
- ///
- public int YMax { get; protected set; }
-
- ///
- ///
- ///
- public bool IsFixedPitch
- {
- get;
- init; // BUG_OLD: never set
- }
-
- ///
- ///
- ///
- public int UnderlinePosition { get; protected set; }
-
- ///
- ///
- ///
- public int UnderlineThickness { get; protected set; }
-
- ///
- ///
- ///
- public int StrikeoutPosition { get; protected set; }
-
- ///
- ///
- ///
- public int StrikeoutSize { get; protected set; }
-
- ///
- ///
- ///
- public string Version
- {
- get;
- private set;
- // BUG_OLD: never set
- } = default!;
-
- ///
- ///
- ///
- public string EncodingScheme
- {
- get;
- private set;
- // BUG_OLD: never set
- } = default!;
-
- ///
- ///
- ///
- public int UnitsPerEm { get; protected set; }
-
- ///
- ///
- ///
- public int CapHeight { get; protected set; }
-
- ///
- ///
- ///
- public int XHeight { get; protected set; }
-
- ///
- ///
- ///
- public int Ascender { get; protected set; }
-
- ///
- ///
- ///
- public int Descender { get; protected set; }
-
- ///
- ///
- ///
- public int Leading { get; protected set; }
-
- ///
- ///
- ///
- public int Flags
- {
- get;
- private set;
- // BUG_OLD: never set
- }
-
- ///
- ///
- ///
- public int StemV { get; protected set; }
-
- ///
- ///
- ///
- public int LineSpacing { get; protected set; }
-
- internal static string ComputeFdKey(string name, XFontStyleEx style)
- => ComputeFdKey(name,
- (style & XFontStyleEx.Bold) != 0,
- (style & XFontStyleEx.Italic) != 0);
-
- internal static string ComputeFdKey(string name, bool isBold, bool isItalic)
- {
- name = name.ToLowerInvariant();
- var key = isBold switch
- {
- false when !isItalic => name + '/',
- true when !isItalic => name + "/b",
- false when isItalic => name + "/i",
- _ => name + "/bi"
- };
- return key;
- }
-
- internal readonly int GlobalVersion = Globals.Global.Version;
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs
deleted file mode 100644
index a161de9d..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs
+++ /dev/null
@@ -1,174 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-#define VERBOSE_
-
-using PdfSharp.Internal;
-using System;
-using System.Collections.Generic;
-
-//using Fixed = System.Int32;
-//using FWord = System.Int16;
-//using UFWord = System.UInt16;
-
-namespace PdfSharp.Fonts.OpenType
-{
- ///
- /// This table contains information that describes the glyphs in the font in the TrueType outline format.
- /// Information regarding the rasterizer (scaler) refers to the TrueType rasterizer.
- /// http://www.microsoft.com/typography/otspec/glyf.htm
- ///
- class GlyphDataTable : OpenTypeFontTable
- {
- public const string Tag = TableTagNames.Glyf;
-
- internal byte[] GlyphTable = default!;
-
- public GlyphDataTable()
- : base(null, Tag)
- {
- DirectoryEntry.Tag = TableTagNames.Glyf;
- }
-
- public GlyphDataTable(OpenTypeFontFace fontData)
- : base(fontData, Tag)
- {
- DirectoryEntry.Tag = TableTagNames.Glyf;
- Read();
- }
-
- ///
- /// Converts the bytes in a handy representation.
- ///
- public void Read()
- {
- try
- {
- // Not yet needed...
- }
- // ReSharper disable once RedundantCatchClause
- catch (Exception)
- {
- throw;
- }
- }
-
- ///
- /// Gets the data of the specified glyph.
- ///
- public byte[] GetGlyphData(int glyph)
- {
- //var loca = _fontData!.loca;
- int start = GetOffset(glyph);
- int next = GetOffset(glyph + 1);
- int count = next - start;
- byte[] bytes = new byte[count];
- Buffer.BlockCopy(_fontData!.FontSource!.Bytes, start, bytes, 0, count);
- return bytes;
- }
-
- ///
- /// Gets the size of the byte array that defines the glyph.
- ///
- public int GetGlyphSize(int glyph)
- {
- //var loca = _fontData.loca;
- return GetOffset(glyph + 1) - GetOffset(glyph);
- }
-
- ///
- /// Gets the offset of the specified glyph relative to the first byte of the font image.
- ///
- public int GetOffset(int glyph)
- {
- return DirectoryEntry.Offset + _fontData!.loca.LocaTable[glyph];
- }
-
- ///
- /// Adds for all composite glyphs, the glyphs the composite one is made of.
- ///
- public void CompleteGlyphClosure(Dictionary glyphs)
- {
- int count = glyphs.Count;
- ushort[] glyphArray = new ushort[glyphs.Count];
- glyphs.Keys.CopyTo(glyphArray, 0);
- // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because of .NET Framework
- if (!glyphs.ContainsKey(0))
- glyphs.Add(0, null);
- // #NFM
- // Ensure no other threads can alter the Position property of this OpenTypeFontFace instance,
- // see https://forum.pdfsharp.net/viewtopic.php?f=2&t=2248#p10378
- try
- {
- Lock.EnterFontFactory();
- for (int idx = 0; idx < count; idx++)
- AddCompositeGlyphs(glyphs, glyphArray[idx]);
- }
- finally { Lock.ExitFontFactory(); }
- }
-
- ///
- /// If the specified glyph is a composite glyph add the glyphs it is made of to the glyph table.
- ///
- void AddCompositeGlyphs(Dictionary glyphs, int glyph)
- {
- //int start = fontData.loca.GetOffset(glyph);
- int start = GetOffset(glyph);
- // Has no contour?
- if (start == GetOffset(glyph + 1))
- return;
- _fontData!.Position = start;
- int numContours = _fontData.ReadShort();
- // Isn’t a composite glyph?
- if (numContours >= 0)
- return;
- _fontData.SeekOffset(8);
- for (; ; )
- {
- int flags = _fontData.ReadUFWord();
- ushort cGlyph = _fontData.ReadUFWord();
- if (!glyphs.ContainsKey(cGlyph))
- glyphs.Add(cGlyph, null);
- if ((flags & MORE_COMPONENTS) == 0)
- return;
- int offset = (flags & ARG_1_AND_2_ARE_WORDS) == 0 ? 2 : 4;
- if ((flags & WE_HAVE_A_SCALE) != 0)
- offset += 2;
- else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0)
- offset += 4;
- if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0)
- offset += 8;
- _fontData.SeekOffset(offset);
- }
- }
-
- ///
- /// Prepares the font table to be compiled into its binary representation.
- ///
- public override void PrepareForCompilation()
- {
- base.PrepareForCompilation();
-
- if (DirectoryEntry.Length == 0)
- DirectoryEntry.Length = GlyphTable.Length;
- DirectoryEntry.CheckSum = CalcChecksum(GlyphTable);
- }
-
- ///
- /// Converts the font into its binary representation.
- ///
- public override void Write(OpenTypeFontWriter writer)
- {
- writer.Write(GlyphTable, 0, DirectoryEntry.PaddedLength);
- }
-
- // ReSharper disable InconsistentNaming
- // Constants from OpenType spec.
- const int ARG_1_AND_2_ARE_WORDS = 1;
- const int WE_HAVE_A_SCALE = 8;
- const int MORE_COMPONENTS = 32;
- const int WE_HAVE_AN_X_AND_Y_SCALE = 64;
- const int WE_HAVE_A_TWO_BY_TWO = 128;
- // ReSharper restore InconsistentNaming
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs
deleted file mode 100644
index 06265755..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using System.Diagnostics.CodeAnalysis;
-using System.Text;
-using PdfSharp.Drawing;
-using PdfSharp.Internal;
-
-namespace PdfSharp.Fonts.OpenType
-{
- ///
- /// Global table of all glyph typefaces.
- ///
- static class GlyphTypefaceCache
- {
- public static bool TryGetGlyphTypeface(string key, [MaybeNullWhen(false)] out XGlyphTypeface glyphTypeface)
- {
- try
- {
- Lock.EnterFontFactory();
- bool result = Globals.Global.Fonts.GlyphTypefacesByKey.TryGetValue(key, out glyphTypeface);
- return result;
- }
- finally { Lock.ExitFontFactory(); }
- }
-
- public static void AddGlyphTypeface(XGlyphTypeface glyphTypeface)
- {
- try
- {
- Lock.EnterFontFactory();
- Debug.Assert(!Globals.Global.Fonts.GlyphTypefacesByKey.ContainsKey(glyphTypeface.Key));
- Globals.Global.Fonts.GlyphTypefacesByKey.Add(glyphTypeface.Key, glyphTypeface);
- }
- finally { Lock.ExitFontFactory(); }
- }
-
- internal static void Reset()
- {
- Globals.Global.Fonts.GlyphTypefacesByKey.Clear();
- }
-
- internal static string GetCacheState()
- {
- var state = new StringBuilder();
- state.Append("====================\n");
- state.Append("Glyph typefaces by name\n");
- Dictionary.KeyCollection familyKeys = Globals.Global.Fonts.GlyphTypefacesByKey.Keys;
- int count = familyKeys.Count;
- string[] keys = new string[count];
- familyKeys.CopyTo(keys, 0);
- Array.Sort(keys, StringComparer.OrdinalIgnoreCase);
- foreach (string key in keys)
- state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.GlyphTypefacesByKey[key].DebuggerDisplay);
- state.Append("\n");
- return state.ToString();
- }
- }
-}
-
-namespace PdfSharp.Internal
-{
- partial class Globals
- {
- partial class FontStorage
- {
- ///
- /// Maps typeface key to glyph typeface.
- ///
- public readonly Dictionary GlyphTypefacesByKey = new(StringComparer.Ordinal);
- }
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontWriter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontWriter.cs
deleted file mode 100644
index fef0bed6..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontWriter.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-namespace PdfSharp.Fonts.OpenType
-{
- ///
- /// Represents a writer for True Type font files.
- ///
- class OpenTypeFontWriter : FontWriter
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public OpenTypeFontWriter(Stream stream)
- : base(stream)
- { }
-
- ///
- /// Writes a table name.
- ///
- public void WriteTag(string tag)
- {
- Debug.Assert(tag.Length == 4);
- WriteByte((byte)(tag[0]));
- WriteByte((byte)(tag[1]));
- WriteByte((byte)(tag[2]));
- WriteByte((byte)(tag[3]));
- }
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs
deleted file mode 100644
index 91c809e6..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using System.Text;
-using PdfSharp.Internal;
-using System.Diagnostics.CodeAnalysis;
-using PdfSharp.Fonts.OpenType;
-
-namespace PdfSharp.Fonts.OpenType
-{
- ///
- /// Global table of all OpenType font faces cached by their face name and check sum.
- ///
- [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
- static class OpenTypeFontFaceCache
- {
- ///
- /// Tries to get font face by its key.
- ///
- public static bool TryGetFontFace(string key,
- [MaybeNullWhen(false)]
- out OpenTypeFontFace fontFace)
- {
- try
- {
- Lock.EnterFontFactory();
- var result = Globals.Global.Fonts.FontFaceCache.TryGetValue(key, out fontFace);
- return result;
- }
- finally { Lock.ExitFontFactory(); }
- }
-
- ///
- /// Tries to get font face by its check sum.
- ///
- public static bool TryGetFontFace(ulong checkSum,
- [MaybeNullWhen(false)]
- out OpenTypeFontFace fontFace)
- {
- try
- {
- Lock.EnterFontFactory();
- var result = Globals.Global.Fonts.FontFacesByCheckSum.TryGetValue(checkSum, out fontFace);
- return result;
- }
- finally { Lock.ExitFontFactory(); }
- }
-
- public static OpenTypeFontFace AddFontFace(OpenTypeFontFace fontFace)
- {
- try
- {
- Lock.EnterFontFactory();
- if (TryGetFontFace(fontFace.FullFaceName, out var fontFaceCheck))
- {
- if (fontFaceCheck.CheckSum != fontFace.CheckSum)
- throw new InvalidOperationException("OpenTypeFontFace with same signature but different bytes.");
- return fontFaceCheck;
- }
- Globals.Global.Fonts.FontFaceCache.Add(fontFace.FullFaceName, fontFace);
- Globals.Global.Fonts.FontFacesByCheckSum.Add(fontFace.CheckSum, fontFace);
- return fontFace;
- }
- finally { Lock.ExitFontFactory(); }
- }
-
- internal static void Reset()
- {
- Globals.Global.Fonts.FontFaceCache.Clear();
- Globals.Global.Fonts.FontFacesByCheckSum.Clear();
- }
-
- internal static string GetCacheState()
- {
- StringBuilder state = new StringBuilder();
- state.Append("====================\n");
- state.Append("OpenType font faces by name\n");
- Dictionary.KeyCollection familyKeys = Globals.Global.Fonts.FontFaceCache.Keys;
- int count = familyKeys.Count;
- string[] keys = new string[count];
- familyKeys.CopyTo(keys, 0);
- Array.Sort(keys, StringComparer.OrdinalIgnoreCase);
- foreach (string key in keys)
- state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontFaceCache[key].DebuggerDisplay);
- state.Append("\n");
- return state.ToString();
- }
-
- ///
- /// Gets the DebuggerDisplayAttribute text.
- ///
- // ReSharper disable UnusedMember.Local
- static string DebuggerDisplay
- // ReSharper restore UnusedMember.Local
- => String.Format(CultureInfo.InvariantCulture, "Font faces: {0}", Globals.Global.Fonts.FontFaceCache.Count);
- }
-}
-
-namespace PdfSharp.Internal
-{
- partial class Globals
- {
- partial class FontStorage
- {
- ///
- /// Maps face name to OpenType font face.
- ///
- public readonly Dictionary FontFaceCache = [];
-
- ///
- /// Maps font source key to OpenType font face.
- ///
- public readonly Dictionary FontFacesByCheckSum = [];
- }
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs
index 83071d6b..3c2444d8 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs
@@ -1,25 +1,24 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using Microsoft.Extensions.Logging;
-using PdfSharp.Fonts.Internal;
-using PdfSharp.Fonts.OpenType;
-using PdfSharp.Logging;
-using PdfSharp.Pdf.Internal;
+using System.Collections;
+using PdfSharp.Internal.OpenType;
namespace PdfSharp.Fonts
{
///
- /// Helper class that determines the characters used in a particular font.
+ /// Helper class that collects the characters and glyphs used in a particular font.
+ /// The glyphs are used in /FontDescriptor for calculating the font subset.
+ /// The characters are used in /TrueType fonts with /WinAnsiEncoding for the /Widths array.
+ /// The characters are used in /Type0 fonts with /Identity-H encoding for /ToUnicode map and
+ /// in the descendant /CIDFontType2 for the /W array.
///
class CMapInfo
{
- public CMapInfo(OpenTypeDescriptor descriptor)
+ public CMapInfo(int glyphCount)
{
- Debug.Assert(descriptor != null);
- _descriptor = descriptor;
+ GlyphIndicesNew = new(glyphCount);
}
- readonly OpenTypeDescriptor _descriptor;
public void AddChars(CodePointGlyphIndexPair[] codePoints)
{
@@ -30,7 +29,22 @@ public void AddChars(CodePointGlyphIndexPair[] codePoints)
var item = codePoints[idx];
if (item.GlyphIndex == 0)
continue;
-
+#if true // #PSGFX
+ if (!CodePointsToGlyphIndices.ContainsKey(item.CodePoint))
+ {
+ CodePointsToGlyphIndices.Add(item.CodePoint, item.GlyphIndex);
+ GlyphIndices[item.GlyphIndex] = null;
+ GlyphIndicesNew[item.GlyphIndex] = true;
+ MinCodePoint = Math.Min(MinCodePoint, item.CodePoint);
+ MaxCodePoint = Math.Max(MaxCodePoint, item.CodePoint);
+ }
+ else
+ {
+ // In PDFsharp Graphics we also can draw glyphs without a code point.
+ GlyphIndices[item.GlyphIndex] = null;
+ GlyphIndicesNew[item.GlyphIndex] = true;
+ }
+#else
if (CodePointsToGlyphIndices.ContainsKey(item.CodePoint))
continue;
@@ -38,6 +52,7 @@ public void AddChars(CodePointGlyphIndexPair[] codePoints)
GlyphIndices[item.GlyphIndex] = default;
MinCodePoint = Math.Min(MinCodePoint, item.CodePoint);
MaxCodePoint = Math.Max(MaxCodePoint, item.CodePoint);
+#endif
}
}
@@ -55,6 +70,9 @@ public int[] Chars
}
}
+ ///
+ /// Gets an ordered array glyph indices.
+ ///
public ushort[] GetGlyphIndices()
{
var indices = new ushort[GlyphIndices.Count];
@@ -63,17 +81,25 @@ public ushort[] GetGlyphIndices()
return indices;
}
- public int MinCodePoint = Int32.MaxValue;
+ public int MinCodePoint = Int32.MaxValue;
public int MaxCodePoint = Int32.MinValue;
///
- /// Maps a Unicode code point to a glyph ID.
+ /// Maps a Unicode code point to a glyphs.
+ /// Contains all used codepoints and their glyphs.
+ /// Used for ToUnicode table and /W entry.
///
- public Dictionary CodePointsToGlyphIndices = [];
+ public readonly Dictionary CodePointsToGlyphIndices = [];
///
- /// Collects all used glyph IDs. Value is not used.
+ /// Collects all used glyph IDs. Value is now used by PDFsharp Graphics where it is possible
+ /// to draw only glyphs without associated code point. Therefore, this dictionary is now
+ /// relevant for computing the font subset.
+ /// We also use this dictionary to fast lookup if a glyph is already added.
///
- public Dictionary GlyphIndices = [];
+ public readonly Dictionary GlyphIndices = [];
+
+ // TODO #PSGFX Use a BitArray for collection the glyphs.
+ public readonly BitArray GlyphIndicesNew;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs
index 9cbe0263..4f61566e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs
@@ -2,77 +2,89 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
-using PdfSharp.Fonts.OpenType;
using PdfSharp.Internal;
+using PdfSharp.Internal.OpenType;
+using PdfSharp.Internal.Threading;
namespace PdfSharp.Fonts
{
///
/// Global table of OpenType font descriptor objects.
///
- static class FontDescriptorCache
+ class PsFontDescriptorCache
{
+ internal PsFontDescriptorCache(PsGlobals.PsFontStorage storage)
+ {
+ _storage = storage;
+ }
+
///
/// Gets the FontDescriptor identified by the specified XFont. If no such object
/// exists, a new FontDescriptor is created and added to the cache.
///
- public static FontDescriptor GetOrCreateDescriptorFor(XFont font)
+ public OpenTypeFontDescriptor GetOrCreateDescriptorFor(XFont font)
{
if (font == null)
throw new ArgumentNullException(nameof(font));
font.GlyphTypeface.CheckVersion();
- //FontSelector1 selector = new FontSelector1(font);
string fontDescriptorKey = font.GlyphTypeface.Key;
try
{
- var cache = Globals.Global.Fonts.FontDescriptorCache;
- Lock.EnterFontFactory();
+ var cache = OtGlobals.Global.OTFonts.FontDescriptorCache; // TODO HACK
+ Locks.EnterFontManagement();
if (cache.TryGetValue(fontDescriptorKey, out var descriptor))
return descriptor;
- descriptor = new OpenTypeDescriptor(fontDescriptorKey, font);
- cache.Add(fontDescriptorKey, descriptor);
+ descriptor = new OpenTypeFontDescriptor(fontDescriptorKey,
+ font.GlyphTypeface.OTFontFace,
+ font.GlyphTypeface.FamilyName,
+ font.GlyphTypeface.FaceName);
+ cache.TryAdd(fontDescriptorKey, descriptor);
return descriptor;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
- public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypeface)
+ public OpenTypeFontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypeface)
{
glyphTypeface.CheckVersion();
string fontDescriptorKey = glyphTypeface.Key;
try
{
- var cache = Globals.Global.Fonts.FontDescriptorCache;
- Lock.EnterFontFactory();
+ var cache = OtGlobals.Global.OTFonts.FontDescriptorCache;
+ Locks.EnterFontManagement();
if (cache.TryGetValue(fontDescriptorKey, out var descriptor))
return descriptor;
- descriptor = new OpenTypeDescriptor(fontDescriptorKey, glyphTypeface);
- cache.Add(fontDescriptorKey, descriptor);
+ descriptor = new OpenTypeFontDescriptor(fontDescriptorKey,
+ glyphTypeface.OTFontFace,
+ glyphTypeface.FamilyName,
+ glyphTypeface.FontName, 42);
+ cache.TryAdd(fontDescriptorKey, descriptor);
return descriptor;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
///
/// Gets the FontDescriptor identified by the specified FontSelector. If no such object
/// exists, a new FontDescriptor is created and added to the stock.
///
- public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontStyleEx style)
+ public OpenTypeFontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontStyleEx style)
{
if (String.IsNullOrEmpty(fontFamilyName))
throw new ArgumentNullException(nameof(fontFamilyName));
//FontSelector1 selector = new FontSelector1(fontFamilyName, style);
- string fontDescriptorKey = FontDescriptor.ComputeFdKey(fontFamilyName, style);
+ string fontDescriptorKey = KeyHelper.ComputeFdKey(fontFamilyName, (OTFontStyleHack)style);
try
{
- var cache = Globals.Global.Fonts.FontDescriptorCache;
- Lock.EnterFontFactory();
+ //var cache = Globals.Global.Fonts.FontDescriptorCache;
+ var cache = OtGlobals.Global.OTFonts.FontDescriptorCache;
+ Locks.EnterFontManagement();
if (!cache.TryGetValue(fontDescriptorKey, out var descriptor))
{
var font = new XFont(fontFamilyName, 10, style);
@@ -81,30 +93,13 @@ public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontS
if (cache.ContainsKey(fontDescriptorKey))
_ = typeof(int); // Just a NOP for a break point.
else
- cache.Add(fontDescriptorKey, descriptor);
+ cache.TryAdd(fontDescriptorKey, descriptor);
}
return descriptor;
}
- finally { Lock.ExitFontFactory(); }
- }
-
- internal static void Reset()
- {
- Globals.Global.Fonts.FontDescriptorCache.Clear();
+ finally { Locks.ExitFontManagement(); }
}
- }
-}
-namespace PdfSharp.Internal
-{
- partial class Globals
- {
- partial class FontStorage
- {
- ///
- /// Maps font descriptor key to font descriptor which is currently only an OpenTypeFontDescriptor.
- ///
- public readonly Dictionary FontDescriptorCache = [];
- }
+ readonly PsGlobals.PsFontStorage _storage;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs
index f03d31bc..31dcb09d 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs
@@ -1,7 +1,15 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using System.Collections.Concurrent;
using System.Text;
+using PdfSharp.Internal;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Internal.OpenType;
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
+using PdfSharp.Drawing;
+using PdfSharp.Fonts.Internal;
#if GDI
using System.Drawing;
using GdiFontFamily = System.Drawing.FontFamily;
@@ -15,20 +23,11 @@
using WpfGlyphTypeface = System.Windows.Media.GlyphTypeface;
using WpfTypeface = System.Windows.Media.Typeface;
#endif
-using PdfSharp.Drawing;
-using PdfSharp.Fonts.Internal;
-using PdfSharp.Fonts.OpenType;
-using PdfSharp.Internal;
-using System.Diagnostics.CodeAnalysis;
-using Microsoft.Extensions.Logging;
-using PdfSharp.Logging;
-
-// Re-Sharper disable RedundantNameQualifier
namespace PdfSharp.Fonts
{
///
- /// Provides functionality to map a font face request to a physical font.
+ /// Provides functionality to map a typeface request to a physical font face.
///
static class FontFactory
{
@@ -49,10 +48,10 @@ static class FontFactory
try
{
- var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName;
- var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName;
+ var fontResolverInfosByName = PsGlobals.Global.Fonts.FontResolverInfosByName;
+ var fontSourcesByName = PsGlobals.Global.Fonts.FontSourcesByName;
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
// Was this typeface requested before?
if (fontResolverInfosByName.TryGetValue(typefaceKey, out var fontResolverInfo))
return fontResolverInfo;
@@ -111,10 +110,16 @@ static class FontFactory
finally
{
_fallbackFontResolverInvoked = false;
- Lock.ExitFontFactory();
+ Locks.ExitFontManagement();
}
}
- static bool _fallbackFontResolverInvoked;
+
+ ///
+ /// A flag that indicated that the fallback font resolver is invoked during a font resolution.
+ /// The flag can safely be a static because it is only temporarily used during font resolving
+ /// and that is always running protected by a lock.
+ ///
+ static bool _fallbackFontResolverInvoked; // Save to be static declaration.
///
/// Register resolver info and font source for a custom font resolver .
@@ -138,10 +143,10 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f
#endif
try
{
- var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName;
- var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName;
+ var fontResolverInfosByName = PsGlobals.Global.Fonts.FontResolverInfosByName;
+ var fontSourcesByName = PsGlobals.Global.Fonts.FontSourcesByName;
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
// OverrideStyleSimulations is true only for internal quality tests.
// With this code we can simulate bold and/or italic for a font face even if
@@ -165,7 +170,7 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f
// Discard new object and reuse previous one.
fontResolverInfo = existingFontResolverInfo;
// Associate existing resolver info with the new typeface key.
- fontResolverInfosByName.Add(typefaceKey, fontResolverInfo);
+ fontResolverInfosByName.TryAdd(typefaceKey, fontResolverInfo);
#if DEBUG_
// The font source should exist.
Debug.Assert(fontResolverInfosByName.ContainsKey(fontResolverInfo.FaceName));
@@ -176,10 +181,10 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f
// Case: No such font resolver info exists.
// Add typeface key to fontResolverInfosByName.
// Thereby resolving a typeface with the same key is not needed anymore.
- fontResolverInfosByName.Add(typefaceKey, fontResolverInfo); // Map TFK immediately to FRI.
+ fontResolverInfosByName.TryAdd(typefaceKey, fontResolverInfo); // Map TFK immediately to FRI.
// Add resolver info key to fontResolverInfosByName.
// Thereby a typeface with the same resolver info as a previous one is not cached twice.
- fontResolverInfosByName.Add(resolverInfoKey, fontResolverInfo);
+ fontResolverInfosByName.TryAdd(resolverInfoKey, fontResolverInfo);
// Create font source if it does not yet exist.
// The face name is considered to be unique. So check if it already exists.
@@ -210,102 +215,26 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f
// custom font resolver returns the same bytes for more than one face name.
var fontSource = XFontSource.GetOrCreateFrom(bytes);
- // Add font source’s font resolver name if it is different to the face name.
- if (String.Compare(fontResolverInfo.FaceName, fontSource.FontName,
+ // Add font source’s font resolver name if it is different to the face key.
+ if (String.Compare(fontResolverInfo.FaceName, fontSource.FontFaceKey,
StringComparison.OrdinalIgnoreCase) != 0)
- fontSourcesByName.Add(fontResolverInfo.FaceName, fontSource);
+ fontSourcesByName.TryAdd(fontResolverInfo.FaceName, fontSource);
}
}
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
-#if GDI
- ///
- /// Registers the font face.
- ///
- // ReSharper disable once UnusedMember.Global
- public static XFontSource RegisterFontFace_unused(byte[] fontBytes)
- {
- try
- {
- var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName;
- var fontSourcesByKey = Globals.Global.Fonts.FontSourcesByKey;
-
- Lock.EnterFontFactory();
- ulong key = FontHelper.CalcChecksum(fontBytes);
- if (fontSourcesByKey.TryGetValue(key, out var fontSource))
- {
- throw new InvalidOperationException("Font face already registered.");
- }
- fontSource = XFontSource.GetOrCreateFrom(fontBytes);
- Debug.Assert(fontSourcesByKey.ContainsKey(key));
- Debug.Assert(fontSource.FontFace != null);
- //fontSource.FontFace = new OpenTypeFontFace(fontSource);
- //FontSourcesByKey.Add(checksum, fontSource);
- //FontSourcesByFontName.Add(fontSource.FontName, fontSource);
-
- XGlyphTypeface glyphTypeface = new XGlyphTypeface(fontSource);
- fontSourcesByName.Add(glyphTypeface.Key, fontSource);
- GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface);
- return fontSource;
- }
- finally { Lock.ExitFontFactory(); }
- }
-#endif
-
- ///
- /// Gets the bytes of a physical font with specified face name.
- ///
- public static XFontSource GetFontSourceByFontName(string fontName)
- {
- if (Globals.Global.Fonts.FontSourcesByName.TryGetValue(fontName, out var fontSource))
- return fontSource;
-
- Debug.Assert(false, $"An XFontSource with the name '{fontName}' does not exist.");
- return null;
- }
-
- ///
- /// Gets the bytes of a physical font with specified face name.
- ///
- public static XFontSource GetFontSourceByTypefaceKey(string typefaceKey)
- {
- if (Globals.Global.Fonts.FontSourcesByName.TryGetValue(typefaceKey, out var fontSource))
- return fontSource;
-
- Debug.Assert(false, $"An XFontSource with the typeface key '{typefaceKey}' does not exist.");
- return null;
- }
-
- public static bool TryGetFontSourceByKey(ulong key, [MaybeNullWhen(false)] out XFontSource fontSource)
- {
- return Globals.Global.Fonts.FontSourcesByKey.TryGetValue(key, out fontSource);
- }
-
- ///
- /// Gets a value indicating whether at least one font source was created.
- ///
- public static bool HasFontSources => Globals.Global.Fonts.FontSourcesByName.Count > 0;
+ // ========== FontResolverInfo ==========
public static bool TryGetFontResolverInfoByTypefaceKey(string typeFaceKey, [MaybeNullWhen(false)] out FontResolverInfo info)
{
- return Globals.Global.Fonts.FontResolverInfosByName.TryGetValue(typeFaceKey, out info);
+ return PsGlobals.Global.Fonts.FontResolverInfosByName.TryGetValue(typeFaceKey, out info);
}
- public static bool TryGetFontSourceByTypefaceKey(string typefaceKey, [MaybeNullWhen(false)] out XFontSource source)
- {
- return Globals.Global.Fonts.FontSourcesByName.TryGetValue(typefaceKey, out source);
- }
-
- //public static bool TryGetFontSourceByFaceName(string faceName, out XFontSource source)
- //{
- // return FontSourcesByName.TryGetValue(faceName, out source);
- //}
-
internal static void CacheFontResolverInfo(string typefaceKey, FontResolverInfo fontResolverInfo)
{
- var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName;
+ var fontResolverInfosByName = PsGlobals.Global.Fonts.FontResolverInfosByName;
// Check whether identical font is already registered.
if (fontResolverInfosByName.TryGetValue(typefaceKey, out _))
{
@@ -318,117 +247,23 @@ internal static void CacheFontResolverInfo(string typefaceKey, FontResolverInfo
throw new InvalidOperationException($"A font resolver already exists with the specified key '{fontResolverInfo.Key}'.");
}
// Add to both dictionaries.
- fontResolverInfosByName.Add(typefaceKey, fontResolverInfo);
- fontResolverInfosByName.Add(fontResolverInfo.Key, fontResolverInfo);
- }
-
- ///
- /// Caches a font source under its face name and its key.
- ///
- public static XFontSource CacheFontSource(XFontSource fontSource)
- {
- try
- {
- Lock.EnterFontFactory();
- // Check whether an identical font source with a different face name already exists.
- if (Globals.Global.Fonts.FontSourcesByKey.TryGetValue(fontSource.Key, out var existingFontSource))
- {
-#if DEBUG
- // Fonts have same length and check sum. Now check byte by byte identity.
- int length = fontSource.Bytes.Length;
- for (int idx = 0; idx < length; idx++)
- {
- if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx])
- {
- //Debug.Assert(false,"Two fonts with identical checksum found.");
- break;
- //goto FontsAreNotIdentical;
- }
- }
- Debug.Assert(existingFontSource.FontFace != null);
-#endif
- return existingFontSource;
-
- //FontsAreNotIdentical:
- //// Incredible rare case: Two different fonts have the same size and check sum.
- //// Give the new one a new key until it do not clash with an existing one.
- //while (FontSourcesByKey.ContainsKey(fontSource.Key))
- // fontSource.IncrementKey();
- }
-
- OpenTypeFontFace? fontFace = fontSource.FontFace;
- if (fontFace == null!)
- {
- // Create OpenType font face for this font source.
- fontSource.FontFace = new OpenTypeFontFace(fontSource);
- }
- Globals.Global.Fonts.FontSourcesByKey.Add(fontSource.Key, fontSource);
- Globals.Global.Fonts.FontSourcesByName.Add(fontSource.FontName, fontSource);
- return fontSource;
- }
- finally { Lock.ExitFontFactory(); }
- }
-
- ///
- /// Caches a font source under its face name and its key.
- ///
- public static XFontSource CacheNewFontSource(string typefaceKey, XFontSource fontSource)
- {
- // Debug.Assert(!FontSourcesByFaceName.ContainsKey(fontSource.FaceName));
-
- // Check whether an identical font source with a different face name already exists.
- if (Globals.Global.Fonts.FontSourcesByKey.TryGetValue(fontSource.Key, out var existingFontSource))
- {
- //// Fonts have same length and check sum. Now check byte by byte identity.
- //int length = fontSource.Bytes.Length;
- //for (int idx = 0; idx < length; idx++)
- //{
- // if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx])
- // {
- // goto FontsAreNotIdentical;
- // }
- //}
- return existingFontSource;
-
- ////// The bytes are really identical. Register font source again with the new face name
- ////// but return the existing one to save memory.
- ////FontSourcesByFaceName.Add(fontSource.FaceName, existingFontSource);
- ////return existingFontSource;
-
- //FontsAreNotIdentical:
- //// Incredible rare case: Two different fonts have the same size and check sum.
- //// Give the new one a new key until it do not clash with an existing one.
- //while (FontSourcesByKey.ContainsKey(fontSource.Key))
- // fontSource.IncrementKey();
- }
-
- OpenTypeFontFace fontFace = fontSource.FontFace;
- if (Equals(fontFace, null))
- {
- fontFace = new OpenTypeFontFace(fontSource);
- fontSource.FontFace = fontFace; // Also sets the font name in fontSource
- }
-
- Globals.Global.Fonts.FontSourcesByName.Add(typefaceKey, fontSource);
- Globals.Global.Fonts.FontSourcesByName.Add(fontSource.FontName, fontSource);
- Globals.Global.Fonts.FontSourcesByKey.Add(fontSource.Key, fontSource);
-
- return fontSource;
+ fontResolverInfosByName.TryAdd(typefaceKey, fontResolverInfo);
+ fontResolverInfosByName.TryAdd(fontResolverInfo.Key, fontResolverInfo);
}
public static void CacheExistingFontSourceWithNewTypefaceKey(string typefaceKey, XFontSource fontSource)
{
try
{
- Lock.EnterFontFactory();
- Globals.Global.Fonts.FontSourcesByName.Add(typefaceKey, fontSource);
+ Locks.EnterFontManagement();
+ PsGlobals.Global.Fonts.FontSourcesByName.TryAdd(typefaceKey, fontSource);
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
public static void CheckInvocationOfPlatformFontResolver()
{
- if (!Lock.IsFontFactoryLookTaken())
+ if (!Locks.IsFontManagementLookTaken())
throw new InvalidOperationException("You must not call PlatformFontResolver.ResolveTypeface if you are not calling from within a font resolver.");
if (_fallbackFontResolverInvoked)
@@ -437,14 +272,18 @@ public static void CheckInvocationOfPlatformFontResolver()
internal static void Reset()
{
- Globals.Global.Fonts.FontResolverInfosByName.Clear();
- Globals.Global.Fonts.FontSourcesByName.Clear();
- Globals.Global.Fonts.FontSourcesByKey.Clear();
-
+ try
+ {
+ Locks.EnterFontManagement();
+ PsGlobals.Global.Fonts.FontResolverInfosByName.Clear();
+ PsGlobals.Global.Fonts.FontSourcesByName.Clear();
+ PsGlobals.Global.Fonts.FontSourcesByKey.Clear();
#if CORE
- // Also requires a reset.
- PlatformFontResolver.Reset();
+ // Also requires a reset.
+ PlatformFontResolver.Reset();
#endif
+ }
+ finally { Locks.ExitFontManagement(); }
}
internal static string GetFontCachesState()
@@ -456,39 +295,39 @@ internal static string GetFontCachesState()
// FontResolverInfo by name.
state.Append("====================\n");
state.Append("Font resolver info by name\n");
- Dictionary.KeyCollection keyCollection = Globals.Global.Fonts.FontResolverInfosByName.Keys;
+ var keyCollection = PsGlobals.Global.Fonts.FontResolverInfosByName.Keys;
count = keyCollection.Count;
keys = new string[count];
keyCollection.CopyTo(keys, 0);
Array.Sort(keys, StringComparer.OrdinalIgnoreCase);
foreach (string key in keys)
- state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontResolverInfosByName[key].DebuggerDisplay);
+ state.AppendFormat(" {0}: {1}\n", key, PsGlobals.Global.Fonts.FontResolverInfosByName[key].DebuggerDisplay);
state.Append('\n');
// FontSource by key.
state.Append("Font source by key and name\n");
- Dictionary.KeyCollection fontSourceKeys = Globals.Global.Fonts.FontSourcesByKey.Keys;
+ var fontSourceKeys = PsGlobals.Global.Fonts.FontSourcesByKey.Keys;
count = fontSourceKeys.Count;
ulong[] ulKeys = new ulong[count];
fontSourceKeys.CopyTo(ulKeys, 0);
Array.Sort(ulKeys, (x, y) => x == y ? 0 : (x > y ? 1 : -1));
foreach (ulong ul in ulKeys)
- state.AppendFormat(" {0}: {1}\n", ul, Globals.Global.Fonts.FontSourcesByKey[ul].DebuggerDisplay);
- Dictionary.KeyCollection fontSourceNames = Globals.Global.Fonts.FontSourcesByName.Keys;
+ state.AppendFormat(" {0}: {1}\n", ul, PsGlobals.Global.Fonts.FontSourcesByKey[ul].DebuggerDisplay);
+ var fontSourceNames = PsGlobals.Global.Fonts.FontSourcesByName.Keys;
count = fontSourceNames.Count;
keys = new string[count];
fontSourceNames.CopyTo(keys, 0);
Array.Sort(keys, StringComparer.OrdinalIgnoreCase);
foreach (string key in keys)
- state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontSourcesByName[key].DebuggerDisplay);
+ state.AppendFormat(" {0}: {1}\n", key, PsGlobals.Global.Fonts.FontSourcesByName[key].DebuggerDisplay);
state.Append("--------------------\n\n");
// FontFamilyInternal by name.
- state.Append(FontFamilyCache.GetCacheState());
+ state.Append(PsFontFamilyCache.GetCacheState());
// XGlyphTypeface by name.
- state.Append(GlyphTypefaceCache.GetCacheState());
+ state.Append(PsGlyphTypefaceCache.GetCacheState());
// OpenTypeFontFace by name.
- state.Append(OpenTypeFontFaceCache.GetCacheState());
+ //state.Append(OpenTypeFontFaceCache.GetCacheState());
return state.ToString();
}
}
@@ -498,26 +337,15 @@ namespace PdfSharp.Internal
{
using Fonts;
- partial class Globals
+ partial class PsGlobals
{
- partial class FontStorage
+ partial class PsFontStorage
{
///
/// Maps typeface key (TFK) to font resolver info (FRI) and
/// maps font resolver key to font resolver info.
///
- public readonly Dictionary FontResolverInfosByName = new(StringComparer.Ordinal);
-
- ///
- /// Maps typeface key or font name to font source.
- ///
- public readonly Dictionary FontSourcesByName = new(StringComparer.OrdinalIgnoreCase);
-
- ///
- /// Maps font source key (FSK) to font source.
- /// The key is a simple hash code of the font face data.
- ///
- public readonly Dictionary FontSourcesByKey = [];
+ public readonly ConcurrentDictionary FontResolverInfosByName = new(StringComparer.Ordinal);
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactoryCaches.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactoryCaches.cs
deleted file mode 100644
index 21f28626..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactoryCaches.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-//using Microsoft.Extensions.Logging;
-//using PdfSharp.Fonts.Internal;
-//using PdfSharp.Fonts.OpenType;
-//using PdfSharp.Logging;
-//using PdfSharp.Pdf.Internal;
-
-//namespace PdfSharp.Fonts
-//{
-// ///
-// /// All font caches comes here.
-// ///
-// static class FontFactoryCaches
-// {
-// public static FontDescriptorCacheType FontDescriptorCache => default!;
-
-// public class FontDescriptorCacheType
-// {
-// }
-// }
-//}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs
index cf907943..da0e2e38 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs
@@ -1,46 +1,52 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using System.Collections.Concurrent;
using System.Text;
+using PdfSharp.Internal;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Fonts;
#if GDI
using GdiFontFamily = System.Drawing.FontFamily;
-using PdfSharp.Fonts.OpenType;
using PdfSharp.Drawing;
#endif
#if WPF
using WpfFontFamily = System.Windows.Media.FontFamily;
#endif
-using PdfSharp.Internal;
-using PdfSharp.Fonts;
namespace PdfSharp.Fonts
{
///
/// Global cache of all internal font family objects.
///
- static class FontFamilyCache
+ class PsFontFamilyCache
{
- public static FontFamilyInternal? GetFamilyByName(string familyName)
+ internal PsFontFamilyCache(PsGlobals.PsFontStorage storage)
+ {
+ _storage = storage;
+ }
+
+ public FontFamilyInternal? GetFamilyByName(string familyName)
{
try
{
- Lock.EnterFontFactory();
- Globals.Global.Fonts.FontFamiliesByName.TryGetValue(familyName, out var family);
+ Locks.EnterFontManagement();
+ _storage.FontFamiliesByName.TryGetValue(familyName, out var family);
return family;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
///
/// Caches the font family or returns a previously cached one.
///
- public static FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFamily)
+ public FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFamily)
{
try
{
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
// Recall that a font family is uniquely identified by its case-insensitive name.
- if (Globals.Global.Fonts.FontFamiliesByName.TryGetValue(fontFamily.Name, out var existingFontFamily))
+ if (_storage.FontFamiliesByName.TryGetValue(fontFamily.Name, out var existingFontFamily))
{
#if DEBUG
if (fontFamily.Name == "xxx")
@@ -48,29 +54,31 @@ public static FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFam
#endif
return existingFontFamily;
}
- Globals.Global.Fonts.FontFamiliesByName.Add(fontFamily.Name, fontFamily);
+ _storage.FontFamiliesByName.TryAdd(fontFamily.Name, fontFamily);
return fontFamily;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
- internal static void Reset()
- {
- Globals.Global.Fonts.FontFamiliesByName.Clear();
- }
+ //internal static void Reset()
+ //{
+ // PsGlobals.Global.Fonts.FontFamiliesByName.Clear();
+ //}
+
+ readonly PsGlobals.PsFontStorage _storage;
internal static string GetCacheState()
{
var state = new StringBuilder();
state.Append("====================\n");
state.Append("Font families by name\n");
- Dictionary.KeyCollection familyKeys = Globals.Global.Fonts.FontFamiliesByName.Keys;
+ var familyKeys = PsGlobals.Global.Fonts.FontFamiliesByName.Keys;
int count = familyKeys.Count;
string[] keys = new string[count];
familyKeys.CopyTo(keys, 0);
Array.Sort(keys, StringComparer.OrdinalIgnoreCase);
foreach (string key in keys)
- state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontFamiliesByName[key].DebuggerDisplay);
+ state.AppendFormat(" {0}: {1}\n", key, PsGlobals.Global.Fonts.FontFamiliesByName[key].DebuggerDisplay);
state.Append("\n");
return state.ToString();
}
@@ -79,14 +87,14 @@ internal static string GetCacheState()
namespace PdfSharp.Internal
{
- partial class Globals
+ partial class PsGlobals
{
- partial class FontStorage
+ partial class PsFontStorage
{
///
/// Maps family name to internal font family.
///
- public readonly Dictionary FontFamiliesByName = [];
+ public readonly ConcurrentDictionary FontFamiliesByName = [];
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs
index 7f0fb8f1..2ce30335 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs
@@ -1,9 +1,11 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using PdfSharp.Drawing;
using PdfSharp.Fonts.Internal;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Internal.OpenType;
using PdfSharp.Internal;
+
#if GDI
using GdiFontFamily = System.Drawing.FontFamily;
#endif
@@ -20,7 +22,7 @@ namespace PdfSharp.Fonts
/// Internal implementation class of XFontFamily.
///
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
- public sealed class FontFamilyInternal
+ internal sealed class FontFamilyInternal
{
// Implementation Notes
// FontFamilyInternal implements an XFontFamily.
@@ -33,7 +35,9 @@ public sealed class FontFamilyInternal
FontFamilyInternal(string familyName, bool createPlatformObjects)
{
- _sourceName = _name = familyName;
+ _sourceName = familyName;
+ _otFontFamily = OpenTypeFontFamily.GetOrCreateFrom(familyName);
+ _name = _otFontFamily.FamilyName;
#if GDI
if (createPlatformObjects)
{
@@ -69,6 +73,7 @@ public sealed class FontFamilyInternal
{
_sourceName = _name = gdiFontFamily.Name;
_gdiFontFamily = gdiFontFamily;
+ _otFontFamily = OpenTypeFontFamily.GetOrCreateFrom(_name);
#if WPF
// Hybrid build only.
_wpfFontFamily = new WpfFontFamily(gdiFontFamily.Name);
@@ -82,6 +87,8 @@ public sealed class FontFamilyInternal
_sourceName = wpfFontFamily.Source;
_name = wpfFontFamily.FamilyNames[FontHelper.XmlLanguageEnUs];
_wpfFontFamily = wpfFontFamily;
+ _otFontFamily = OpenTypeFontFamily.GetOrCreateFrom(_name);
+
#if GDI
// Hybrid build only.
_gdiFontFamily = new GdiFontFamily(_sourceName);
@@ -93,16 +100,17 @@ internal static FontFamilyInternal GetOrCreateFromName(string familyName, bool c
{
try
{
- Lock.EnterFontFactory();
- var family = FontFamilyCache.GetFamilyByName(familyName);
+ Locks.EnterFontManagement();
+ var fontFamilyCache = PsGlobals.Global.Fonts.FontFamilyCache;
+ var family = fontFamilyCache.GetFamilyByName(familyName);
if (family == null)
{
- family = new FontFamilyInternal(familyName, createPlatformObject);
- family = FontFamilyCache.CacheOrGetFontFamily(family);
+ family = new(familyName, createPlatformObject);
+ family = fontFamilyCache.CacheOrGetFontFamily(family);
}
return family;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
#if GDI
@@ -110,12 +118,13 @@ internal static FontFamilyInternal GetOrCreateFromGdi(GdiFontFamily gdiFontFamil
{
try
{
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
FontFamilyInternal fontFamily = new FontFamilyInternal(gdiFontFamily);
- fontFamily = FontFamilyCache.CacheOrGetFontFamily(fontFamily);
+ var fontFamilyCache = PsGlobals.Global.Fonts.FontFamilyCache;
+ fontFamily = fontFamilyCache.CacheOrGetFontFamily(fontFamily);
return fontFamily;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
#endif
@@ -123,7 +132,8 @@ internal static FontFamilyInternal GetOrCreateFromGdi(GdiFontFamily gdiFontFamil
internal static FontFamilyInternal GetOrCreateFromWpf(WpfFontFamily wpfFontFamily)
{
FontFamilyInternal fontFamily = new FontFamilyInternal(wpfFontFamily);
- fontFamily = FontFamilyCache.CacheOrGetFontFamily(fontFamily);
+ var fontFamilyCache = PsGlobals.Global.Fonts.FontFamilyCache;
+ fontFamily = fontFamilyCache.CacheOrGetFontFamily(fontFamily);
return fontFamily;
}
#endif
@@ -132,16 +142,15 @@ internal static FontFamilyInternal GetOrCreateFromWpf(WpfFontFamily wpfFontFamil
/// Gets the family name this family was originally created with.
///
public string SourceName => _sourceName;
-
readonly string _sourceName;
///
/// Gets the name that uniquely identifies this font family.
///
public string Name => _name;
-
readonly string _name;
+ readonly OpenTypeFontFamily _otFontFamily;
#if GDI
///
/// Gets the underlying GDI+ font family object.
@@ -166,6 +175,6 @@ internal static FontFamilyInternal GetOrCreateFromWpf(WpfFontFamily wpfFontFamil
/// Gets the DebuggerDisplayAttribute text.
///
internal string DebuggerDisplay
- => String.Format(CultureInfo.InvariantCulture, "FontFamily: '{0}'", Name);
+ => String.Format(CultureInfo.InvariantCulture, "FontFamily: '{0}'", _otFontFamily.FamilyName);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontSourceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontSourceCache.cs
new file mode 100644
index 00000000..7dd9f92a
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontSourceCache.cs
@@ -0,0 +1,200 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Drawing;
+using PdfSharp.Internal;
+using PdfSharp.Internal.OpenType;
+using PdfSharp.Internal.Threading;
+using System.Collections.Concurrent;
+#if GDI
+using GdiFontFamily = System.Drawing.FontFamily;
+#endif
+#if WPF
+using WpfFontFamily = System.Windows.Media.FontFamily;
+#endif
+
+namespace PdfSharp.Fonts
+{
+ ///
+ /// Global cache of all internal font family objects.
+ ///
+ class PsFontSourceCache
+ {
+ internal PsFontSourceCache(PsGlobals.PsFontStorage storage)
+ {
+ _storage = storage;
+ }
+
+ ///
+ /// Gets the bytes of a physical font with specified face name.
+ ///
+ public XFontSource GetFontSourceByFontName(string uniqueFontSourceName)
+ {
+ if (_storage.FontSourcesByName.TryGetValue(uniqueFontSourceName, out var fontSource))
+ return fontSource;
+
+ Debug.Assert(false, $"An XFontSource with the name '{uniqueFontSourceName}' does not exist.");
+ return null;
+ }
+
+ ///
+ /// Gets the bytes of a physical font with specified face name.
+ ///
+ public XFontSource GetFontSourceByTypefaceKey(string typefaceKey)
+ {
+ if (_storage.FontSourcesByName.TryGetValue(typefaceKey, out var fontSource))
+ return fontSource;
+
+ Debug.Assert(false, $"An XFontSource with the typeface key '{typefaceKey}' does not exist.");
+ return null;
+ }
+
+ public bool TryGetFontSourceByKey(ulong key, [MaybeNullWhen(false)] out XFontSource fontSource)
+ {
+ return _storage.FontSourcesByKey.TryGetValue(key, out fontSource);
+ }
+
+ public bool TryGetFontSourceByTypefaceKey(string typefaceKey, [MaybeNullWhen(false)] out XFontSource source)
+ {
+ return _storage.FontSourcesByName.TryGetValue(typefaceKey, out source);
+ }
+
+ ///
+ /// Caches a font source under its face name and its key.
+ ///
+ public XFontSource CacheFontSource(XFontSource fontSource)
+ {
+ try
+ {
+ Locks.EnterFontManagement();
+ // Check whether an identical font source with a different face name already exists.
+ if (_storage.FontSourcesByKey.TryGetValue(fontSource.ChecksumKey, out var existingFontSource))
+ {
+#if DEBUG
+ // Fonts have same length and check sum. Now check byte by byte identity.
+ int length = fontSource.Bytes.Length;
+ for (int idx = 0; idx < length; idx++)
+ {
+ if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx])
+ {
+ //Debug.Assert(false,"Two fonts with identical checksum found.");
+ break;
+ //goto FontsAreNotIdentical;
+ }
+ }
+ Debug.Assert(existingFontSource.OTFontFace != null);
+#endif
+ return existingFontSource;
+
+ //FontsAreNotIdentical:
+ //// Incredible rare case: Two different fonts have the same size and check sum.
+ //// Give the new one a new key until it do not clash with an existing one.
+ //while (FontSourcesByKey.ContainsKey(fontSource.Key))
+ // fontSource.IncrementKey();
+ }
+
+ OpenTypeFontFace? fontFace = fontSource.OTFontFace;
+ if (fontFace == null!)
+ {
+ // Create OpenType font face for this font source.
+ fontSource.OTFontFace = OpenTypeFontFace.GetOrCreateFrom(fontSource.OTFontSource);
+ }
+ _storage.FontSourcesByKey.TryAdd(fontSource.ChecksumKey, fontSource);
+ _storage.FontSourcesByName.TryAdd(fontSource.FontFaceKey, fontSource);
+ return fontSource;
+ }
+ finally { Locks.ExitFontManagement(); }
+ }
+
+ ///
+ /// Caches a font source under its face name and its key.
+ ///
+ public XFontSource CacheNewFontSource(string typefaceKey, XFontSource fontSource)
+ {
+ // Debug.Assert(!FontSourcesByFaceName.ContainsKey(fontSource.FaceName));
+
+ // Check whether an identical font source with a different face name already exists.
+ if (_storage.FontSourcesByKey.TryGetValue(fontSource.ChecksumKey, out var existingFontSource))
+ {
+ //// Fonts have same length and check sum. Now check byte by byte identity.
+ //int length = fontSource.Bytes.Length;
+ //for (int idx = 0; idx < length; idx++)
+ //{
+ // if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx])
+ // {
+ // goto FontsAreNotIdentical;
+ // }
+ //}
+ return existingFontSource;
+
+ ////// The bytes are really identical. Register font source again with the new face name
+ ////// but return the existing one to save memory.
+ ////FontSourcesByFaceName.Add(fontSource.FaceName, existingFontSource);
+ ////return existingFontSource;
+
+ //FontsAreNotIdentical:
+ //// Incredible rare case: Two different fonts have the same size and check sum.
+ //// Give the new one a new key until it do not clash with an existing one.
+ //while (FontSourcesByKey.ContainsKey(fontSource.Key))
+ // fontSource.IncrementKey();
+ }
+
+ OpenTypeFontFace otFontFace = fontSource.OTFontFace;
+ if (Equals(otFontFace, null))
+ {
+ otFontFace = OpenTypeFontFace.GetOrCreateFrom(fontSource.OTFontSource);
+ fontSource.OTFontFace = otFontFace; // Also sets the font name in fontSource
+ }
+
+ _storage.FontSourcesByName.TryAdd(typefaceKey, fontSource);
+ _storage.FontSourcesByName.TryAdd(fontSource.FontFaceKey, fontSource);
+ _storage.FontSourcesByKey.TryAdd(fontSource.ChecksumKey, fontSource);
+
+ return fontSource;
+ }
+
+ ///
+ /// Gets a value indicating whether at least one font source was created.
+ ///
+ public bool HasFontSources => _storage.FontSourcesByName.Count > 0;
+
+
+ readonly PsGlobals.PsFontStorage _storage;
+
+ //internal static string GetCacheState()
+ //{
+ // var state = new StringBuilder();
+ // state.Append("====================\n");
+ // state.Append("Font families by name\n");
+ // var familyKeys = PsGlobals.Global.Fonts.FontFamiliesByName.Keys;
+ // int count = familyKeys.Count;
+ // string[] keys = new string[count];
+ // familyKeys.CopyTo(keys, 0);
+ // Array.Sort(keys, StringComparer.OrdinalIgnoreCase);
+ // foreach (string key in keys)
+ // state.AppendFormat(" {0}: {1}\n", key, _storage.FontFamiliesByName[key].DebuggerDisplay);
+ // state.Append("\n");
+ // return state.ToString();
+ //}
+ }
+}
+
+namespace PdfSharp.Internal
+{
+ partial class PsGlobals
+ {
+ partial class PsFontStorage
+ {
+ ///
+ /// Maps font source key (FSK) to font source.
+ /// The key is a simple hash code of the font face data.
+ ///
+ public readonly ConcurrentDictionary FontSourcesByKey = [];
+
+ ///
+ /// Maps typeface key or font name to font source.
+ ///
+ public readonly ConcurrentDictionary FontSourcesByName = new(StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs
index 987e7bd5..9f1fca85 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs
@@ -2,8 +2,9 @@
// See the LICENSE file in the solution root for more information.
using Microsoft.Extensions.Logging;
+using PdfSharp.Internal.OpenType;
+using PdfSharp.Internal.Threading;
using PdfSharp.Fonts;
-using PdfSharp.Fonts.OpenType;
using PdfSharp.Internal;
using PdfSharp.Logging;
using PdfSharp.Pdf;
@@ -19,7 +20,7 @@ public static class GlobalFontSettings
/// The name of the default font. This name is obsolete and must not be used anymore.
///
[Obsolete("DefaultFontName is deprecated. Do not use it anymore.")]
- public const string DefaultFontName_ = "PlatformDefault";
+ public const string DefaultFontName = "PlatformDefault";
///
/// Gets or sets the custom font resolver for the current application.
@@ -29,20 +30,20 @@ public static class GlobalFontSettings
///
public static IFontResolver? FontResolver
{
- get => Globals.Global.Fonts.FontResolver;
+ get => PsGlobals.Global.Fonts.FontResolver;
set
{
// Cannot remove font resolver.
if (value == null)
throw new ArgumentNullException(nameof(value), "You cannot remove the font resolver.");
- ref var fontResolver = ref Globals.Global.Fonts.FontResolver;
+ ref var fontResolver = ref PsGlobals.Global.Fonts.FontResolver;
try
{
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
SetFontResolver(value, ref fontResolver);
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
}
@@ -54,20 +55,20 @@ public static IFontResolver? FontResolver
///
public static IFontResolver? FallbackFontResolver
{
- get => Globals.Global.Fonts.FallbackFontResolver;
+ get => PsGlobals.Global.Fonts.FallbackFontResolver;
set
{
// Cannot remove font resolver.
if (value == null)
throw new ArgumentNullException(nameof(value), "You cannot remove the fallback font resolver.");
- ref var fontResolver = ref Globals.Global.Fonts.FallbackFontResolver;
+ ref var fontResolver = ref PsGlobals.Global.Fonts.FallbackFontResolver;
try
{
- Lock.EnterFontFactory();
+ Locks.EnterFontManagement();
SetFontResolver(value, ref fontResolver);
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
}
@@ -89,7 +90,8 @@ static void SetFontResolver(IFontResolver value, ref IFontResolver? location)
return;
}
- if (FontFactory.HasFontSources)
+ var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache;
+ if (fontSourceCache.HasFontSources)
{
#if DEBUG
var config = Capabilities.Build.BuildName;
@@ -140,15 +142,12 @@ internal static void ResetAll(bool calledFromResetFontManagement = false)
if (calledFromResetFontManagement)
PdfSharpLogHost.Logger.LogInformation("PDFsharp font management is about to be reset.");
- Globals.Global.Fonts.FontResolver = null;
- Globals.Global.Fonts.FallbackFontResolver = null;
- Globals.Global.Fonts.UseWindowsFontsUnderWindows = null;
- Globals.Global.Fonts.UseWindowsFontsUnderWsl2 = null;
- GlyphTypefaceCache.Reset();
- FontDescriptorCache.Reset();
+ PsGlobals.Global.Fonts.FontResolver = null;
+ PsGlobals.Global.Fonts.FallbackFontResolver = null;
+ PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows = null;
+ PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 = null;
FontFactory.Reset();
- FontFamilyCache.Reset();
- OpenTypeFontFaceCache.Reset();
+ OtGlobals.RecreateOtGlobals();
}
///
@@ -162,27 +161,27 @@ public static PdfFontEncoding DefaultFontEncoding
{
get
{
- if (!Globals.Global.Fonts.FontEncodingInitialized)
+ if (!PsGlobals.Global.Fonts.FontEncodingInitialized)
DefaultFontEncoding = PdfFontEncoding.Automatic;
- return Globals.Global.Fonts.FontEncoding;
+ return PsGlobals.Global.Fonts.FontEncoding;
}
set
{
try
{
- Lock.EnterFontFactory();
- if (Globals.Global.Fonts.FontEncodingInitialized)
+ Locks.EnterFontManagement();
+ if (PsGlobals.Global.Fonts.FontEncodingInitialized)
{
// Ignore multiple setting e.g. in a web application.
- if (Globals.Global.Fonts.FontEncoding == value)
+ if (PsGlobals.Global.Fonts.FontEncoding == value)
return;
throw new InvalidOperationException("Must not change DefaultFontEncoding after it was set once.");
}
- Globals.Global.Fonts.FontEncoding = value;
- Globals.Global.Fonts.FontEncodingInitialized = true;
+ PsGlobals.Global.Fonts.FontEncoding = value;
+ PsGlobals.Global.Fonts.FontEncodingInitialized = true;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
}
@@ -198,26 +197,26 @@ public static bool UseWindowsFontsUnderWindows
get
{
// If not opted-in we do not use Windows fonts anymore.
- if (Globals.Global.Fonts.UseWindowsFontsUnderWindows == null)
+ if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows == null)
UseWindowsFontsUnderWindows = false;
- return Globals.Global.Fonts.UseWindowsFontsUnderWindows ?? false;
+ return PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows ?? false;
}
set
{
try
{
- Lock.EnterFontFactory();
- if (Globals.Global.Fonts.UseWindowsFontsUnderWindows.HasValue)
+ Locks.EnterFontManagement();
+ if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows.HasValue)
{
// Ignore multiple setting e.g. in a web application.
- if (Globals.Global.Fonts.UseWindowsFontsUnderWindows == value)
+ if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows == value)
return;
throw new InvalidOperationException("Must not change UseWindowsFontsUnderWindows after it was once set or got.");
}
- Globals.Global.Fonts.UseWindowsFontsUnderWindows = value;
+ PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows = value;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
}
@@ -232,28 +231,29 @@ public static bool UseWindowsFontsUnderWsl2
get
{
// If not opted-in we do not use Windows fonts anymore.
- if (Globals.Global.Fonts.UseWindowsFontsUnderWsl2 == null)
+ if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 == null)
UseWindowsFontsUnderWsl2 = false;
- return Globals.Global.Fonts.UseWindowsFontsUnderWsl2 ?? false;
+ return PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 ?? false;
}
set
{
try
{
- Lock.EnterFontFactory();
- if (Globals.Global.Fonts.UseWindowsFontsUnderWsl2.HasValue)
+ Locks.EnterFontManagement();
+ if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2.HasValue)
{
// Ignore multiple setting e.g. in a web application.
- if (Globals.Global.Fonts.UseWindowsFontsUnderWsl2 == value)
+ if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 == value)
return;
throw new InvalidOperationException("Must not change UseWindowsFontsUnderWsl2 after it was once set or got.");
}
- Globals.Global.Fonts.UseWindowsFontsUnderWsl2 = value;
+ PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 = value;
}
- finally { Lock.ExitFontFactory(); }
+ finally { Locks.ExitFontManagement(); }
}
}
+
#elif GDI || WPF
///
/// This property has no effect under a GDI or WPF build of PDFsharp.
@@ -292,21 +292,21 @@ public static bool UseWindowsFontsUnderWsl2
internal static void Reset()
{
// Reset font resolvers.
- Globals.Global.Fonts.FontResolver = null;
- Globals.Global.Fonts.FallbackFontResolver = null;
+ PsGlobals.Global.Fonts.FontResolver = null;
+ PsGlobals.Global.Fonts.FallbackFontResolver = null;
// Reset mode of PlatformFontResolver.
- Globals.Global.Fonts.UseWindowsFontsUnderWindows = null;
- Globals.Global.Fonts.UseWindowsFontsUnderWsl2 = null;
+ PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows = null;
+ PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 = null;
}
}
}
namespace PdfSharp.Internal
{
- partial class Globals
+ partial class PsGlobals
{
- partial class FontStorage
+ partial class PsFontStorage
{
///
/// The globally set custom font resolver.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphHelper.cs
index 8dca01a3..07136d28 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphHelper.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphHelper.cs
@@ -1,6 +1,11 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using System.Text;
+using PdfSharp.Pdf.Internal;
+using PdfSharp.Drawing;
+using PdfSharp.Fonts.Internal;
+using PdfSharp.Internal.OpenType;
#if GDI
using System.Drawing;
using System.Drawing.Text;
@@ -8,10 +13,6 @@
#if WPF
using System.Windows.Media;
#endif
-using System.Text;
-using PdfSharp.Drawing;
-using PdfSharp.Fonts.Internal;
-using PdfSharp.Pdf.Internal;
namespace PdfSharp.Fonts
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphTypefaceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphTypefaceCache.cs
new file mode 100644
index 00000000..c37ca9bf
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphTypefaceCache.cs
@@ -0,0 +1,76 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using System.Collections.Concurrent;
+using System.Text;
+using PdfSharp.Internal;
+using PdfSharp.Internal.Threading;
+using PdfSharp.Drawing;
+
+namespace PdfSharp.Fonts
+{
+ ///
+ /// Global table of all glyph typefaces.
+ ///
+ class PsGlyphTypefaceCache
+ {
+ internal PsGlyphTypefaceCache(PsGlobals.PsFontStorage storage)
+ {
+ _storage = storage;
+ }
+
+ public bool TryGetGlyphTypeface(string key, [MaybeNullWhen(false)] out XGlyphTypeface glyphTypeface)
+ {
+ try
+ {
+ Locks.EnterFontManagement();
+ bool result = _storage.GlyphTypefacesByKey.TryGetValue(key, out glyphTypeface);
+ return result;
+ }
+ finally { Locks.ExitFontManagement(); }
+ }
+
+ public void AddGlyphTypeface(XGlyphTypeface glyphTypeface)
+ {
+ try
+ {
+ Locks.EnterFontManagement();
+ Debug.Assert(!_storage.GlyphTypefacesByKey.ContainsKey(glyphTypeface.Key));
+ _storage.GlyphTypefacesByKey.TryAdd(glyphTypeface.Key, glyphTypeface);
+ }
+ finally { Locks.ExitFontManagement(); }
+ }
+
+ readonly PsGlobals.PsFontStorage _storage;
+
+ internal static string GetCacheState()
+ {
+ var state = new StringBuilder();
+ state.Append("====================\n");
+ state.Append("Glyph typefaces by name\n");
+ var familyKeys = PsGlobals.Global.Fonts.GlyphTypefacesByKey.Keys;
+ int count = familyKeys.Count;
+ string[] keys = new string[count];
+ familyKeys.CopyTo(keys, 0);
+ Array.Sort(keys, StringComparer.OrdinalIgnoreCase);
+ foreach (string key in keys)
+ state.AppendFormat(" {0}: {1}\n", key, PsGlobals.Global.Fonts.GlyphTypefacesByKey[key].DebuggerDisplay);
+ state.Append("\n");
+ return state.ToString();
+ }
+ }
+}
+
+namespace PdfSharp.Internal
+{
+ partial class PsGlobals
+ {
+ partial class PsFontStorage
+ {
+ ///
+ /// Maps typeface key to glyph typeface.
+ ///
+ public readonly ConcurrentDictionary GlyphTypefacesByKey = new(StringComparer.Ordinal);
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/PlatformFontResolver.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/PlatformFontResolver.cs
index b392a126..eb3a1565 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/PlatformFontResolver.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/PlatformFontResolver.cs
@@ -106,8 +106,8 @@ public static class PlatformFontResolver
else
{
#if GDI && !WPF
- bool mustSimulateBold = gdiFont.Bold && !fontSource.FontFace.os2.IsBold;
- bool mustSimulateItalic = gdiFont.Italic && !fontSource.FontFace.os2.IsItalic;
+ bool mustSimulateBold = gdiFont.Bold && !fontSource.OTFontFace.os2.IsBold;
+ bool mustSimulateItalic = gdiFont.Italic && !fontSource.OTFontFace.os2.IsItalic;
fontResolverInfo = new PlatformFontResolverInfo(typefaceKey, mustSimulateBold, mustSimulateItalic, gdiFont);
#endif
#if WPF
@@ -168,8 +168,8 @@ public static class PlatformFontResolver
typefaceKey = XGlyphTypeface.ComputeGtfKey(familyName, fontResolvingOptions);
var style = fontResolvingOptions.FontStyle;
- var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName;
- var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName;
+ var fontResolverInfosByName = PsGlobals.Global.Fonts.FontResolverInfosByName;
+ var fontSourcesByName = PsGlobals.Global.Fonts.FontSourcesByName;
// Was this typeface requested before?
if (fontResolverInfosByName.TryGetValue(typefaceKey, out var fontResolverInfo))
@@ -314,11 +314,6 @@ internal static void Reset()
typefaceKey = XGlyphTypeface.ComputeGtfKey(familyName, fontResolvingOptions);
XFontStyleEx style = fontResolvingOptions.FontStyle;
-#if DEBUG_
- if (StringComparer.OrdinalIgnoreCase.Compare(familyName, "Segoe UI Semilight") == 0
- && (style & XFontStyleEx.BoldItalic) == XFontStyleEx.Italic)
- familyName.GetType();
-#endif
wpfFontFamily = null;
wpfTypeface = null;
wpfGlyphTypeface = null;
@@ -326,21 +321,6 @@ internal static void Reset()
// Use WPF technique to create font data.
// No more XPrivateFontCollection.
//wpfTypeface = XPrivateFontCollection.TryCreateTypeface(familyName, style, out wpfFontFamily);
-#if DEBUG_
- if (wpfTypeface != null)
- {
- WpfGlyphTypeface glyphTypeface;
- ICollection list = wpfFontFamily.GetTypefaces();
- foreach (WpfTypeface tf in list)
- {
- if (!tf.TryGetGlyphTypeface(out glyphTypeface))
- Debug-Break.Break();
- }
-
- //if (!WpfTypeface.TryGetGlyphTypeface(out glyphTypeface))
- // throw new InvalidOperationException(PsMgs.CannotGetGlyphTypeface(familyName));
- }
-#endif
wpfFontFamily ??= new WpfFontFamily(familyName);
wpfTypeface ??= FontHelper.CreateTypeface(wpfFontFamily, style);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/WindowsPlatformFontResolver.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/WindowsPlatformFontResolver.cs
index 7c2887b1..286d765a 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/WindowsPlatformFontResolver.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/WindowsPlatformFontResolver.cs
@@ -106,7 +106,7 @@ public enum FontSimulation
public byte[]? GetFont(string faceName)
{
- // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract because it can be null.
+ // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract because it can yet be null.
_fontsPath ??= Capabilities.OperatingSystem.IsWindows
? WindowsFontsPath
: WindowsFontsPathUnderWsl2;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Calc.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Calc.cs
index 620801d7..6ca4bc50 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Calc.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Calc.cs
@@ -17,7 +17,7 @@ static class Calc
///
/// Degree to radiant factor.
///
- public const double Deg2Rad = Math.PI / 180;
+ public static double Deg2Rad => Math.PI / 180;
///
/// Get page size in point from specified PageSize.
@@ -48,6 +48,31 @@ public static XSize PageSizeToSize(PageSize value)
PageSize.Quarto => new(576, 720),
PageSize.Size10x14 => new(720, 1008),
+#if true_ // Who needs this? Should be deleted.
+ PageSize.Undefined => expr,
+ PageSize.RA0 => expr,
+ PageSize.RA1 => expr,
+ PageSize.RA2 => expr,
+ PageSize.RA3 => expr,
+ PageSize.RA4 => expr,
+ PageSize.RA5 => expr,
+ PageSize.B0 => expr,
+ PageSize.B1 => expr,
+ PageSize.B2 => expr,
+ PageSize.B3 => expr,
+ PageSize.Foolscap => expr,
+ PageSize.GovernmentLetter => expr,
+ PageSize.Post => expr,
+ PageSize.Crown => expr,
+ PageSize.LargePost => expr,
+ PageSize.Demy => expr,
+ PageSize.Medium => expr,
+ PageSize.Royal => expr,
+ PageSize.Elephant => expr,
+ PageSize.DoubleDemy => expr,
+ PageSize.QuadDemy => expr,
+ PageSize.STMT => expr,
+#endif
_ => throw new ArgumentException("Invalid PageSize.")
};
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Diagnostics.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Diagnostics.cs
index 61b62773..8f6ddbe0 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Diagnostics.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Diagnostics.cs
@@ -2,6 +2,7 @@
// See the LICENSE file in the solution root for more information.
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using PdfSharp.Pdf;
using PdfSharp.Pdf.Content;
@@ -56,16 +57,19 @@ enum NotSupportedBehavior
static class ParserDiagnostics // #CLEANUP
{
+ [DoesNotReturn]
public static void ThrowParserException(string message)
{
throw new PdfReaderException(message);
}
+ [DoesNotReturn]
public static void ThrowParserException(string message, Exception innerException)
{
throw new PdfReaderException(message, innerException);
}
+ [DoesNotReturn]
public static void HandleUnexpectedCharacter(char ch, string dump)
{
// Hex formatting does not work with type Char. It must be cast to integer.
@@ -75,6 +79,7 @@ public static void HandleUnexpectedCharacter(char ch, string dump)
dump;
ThrowParserException(message);
}
+ [DoesNotReturn]
public static void HandleUnexpectedToken(string token, string dump)
{
string message =
@@ -84,6 +89,7 @@ public static void HandleUnexpectedToken(string token, string dump)
ThrowParserException(message);
}
+ [DoesNotReturn]
public static void CannotFindEndOfStream(PdfDictionary dict)
{
string message = $"Cannot find end of stream in object '{dict.ObjectID}'.";
@@ -93,22 +99,26 @@ public static void CannotFindEndOfStream(PdfDictionary dict)
static class ContentReaderDiagnostics // #CLEANUP
{
+ [DoesNotReturn]
public static void ThrowContentReaderException(string message)
{
throw new ContentReaderException(message);
}
+ [DoesNotReturn]
public static void ThrowContentReaderException(string message, Exception innerException)
{
throw new ContentReaderException(message, innerException);
}
+ [DoesNotReturn]
public static void ThrowNumberOutOfIntegerRange(long value)
{
string message = String.Format(CultureInfo.InvariantCulture, "Number '{0}' out of integer range.", value);
ThrowContentReaderException(message);
}
+ [DoesNotReturn]
public static void HandleUnexpectedCharacter(char ch)
{
string message = String.Format(CultureInfo.InvariantCulture,
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DiagnosticsHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DiagnosticsHelper.cs
index c72ebaaf..fceafd22 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DiagnosticsHelper.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DiagnosticsHelper.cs
@@ -122,8 +122,8 @@ public static (string Stretch, string Weight) TryGetStretchAndWeight(XGlyphTypef
{
var bold = glyphTypeface.IsBold ? " (bold)" : "";
- var stretch = XFontStretches.FontStretchFromFaceName(glyphTypeface.FaceName);
- var weight = XFontWeights.FontWeightFromFaceName(glyphTypeface.FaceName);
+ var stretch = XFontStretches.FontStretchFromFaceName(glyphTypeface.FontName);
+ var weight = XFontWeights.FontWeightFromFaceName(glyphTypeface.FontName);
return (stretch.ToString(), weight.ToString() + bold);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs
index e8d9f177..9db47cd6 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs
@@ -1,50 +1,60 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
+// DELETE
-#if !NET6_0_OR_GREATER
+////// PDFsharp - A .NET library for processing PDF
+////// See the LICENSE file in the solution root for more information.
-using System.Numerics;
+////#if !NET8_0_OR_GREATER
-namespace PdfSharp.Internal
-{
- ///
- /// Class containing replacements for net 6 methods missing in net framework 4.6.2.
- ///
- static class DotNetHelper
- {
- ///
- /// Implements the net 6 BigInteger constructor missing in net framework 4.6.2.
- /// Initializes a new instance of the BigInteger structure using the values in a read-only span of bytes, and optionally indicating the signing encoding and the endianness byte order.
- ///
- ///
- ///
- ///
- public static BigInteger CreateBigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigEndian = false)
- {
- var bytes = value.ToArray();
+////using System;
+////using System.Collections;
+////using System.Collections.Generic;
+////using System.Numerics;
- // Convert to little endian, which is expected by BigInteger constructor.
- if (isBigEndian)
- bytes = bytes.Reverse().ToArray();
+////namespace PdfSharp.Internal
+////{
+//// ///
+//// /// Class containing replacements for .NET 6 methods missing in .NET framework 4.6.2.
+//// ///
+//// static class DotNetHelper
+//// {
+//// ///
+//// /// Implements the .NET 6 BigInteger constructor missing in .NET framework 4.6.2.
+//// /// Initializes a new instance of the BigInteger structure using the values in a read-only span of bytes, and optionally indicating the signing encoding and the endianness byte order.
+//// ///
+//// ///
+//// ///
+//// ///
+//// public static BigInteger CreateBigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigEndian = false)
+//// {
+//// var bytes = value.ToArray();
- // A leading bit of 1 defines a negative number. If the input should be interpreted as unsigned, prepend a new zero byte, if there’s a leading 1.
- // As bytes is in little endian order, check the most significant bit of the last byte. If it is 1, append the zero byte.
- if (isUnsigned && bytes.Length > 0 && (bytes.Last() & 0x80) > 0)
- {
-#if NET462
- var len = bytes.Length;
- var bytes2 = new byte[len + 1];
- bytes.CopyTo(bytes2, 0);
- bytes2[len] = 0;
- bytes = bytes2;
-#else
- bytes = bytes.Append((byte)0).ToArray();
-#endif
- }
+//// // Convert to little endian, which is expected by BigInteger constructor.
+//// if (isBigEndian)
+//// {
+//// //bytes = bytes.Reverse().ToArray();
+//// // In .NET 10 is for 'bytes.Reverse()' 'MemoryExtensions.Reverse(this Span)' resolved
+//// // instead of 'Enumerable.Reverse(this IEnumerable)'.
+//// // See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/breaking-changes/compiler%20breaking%20changes%20-%20dotnet%2010#enumerablereverse
+//// // ReSharper disable once InvokeAsExtensionMethod because we must call the extension method explicitly in .NET 10.
+//// bytes = Enumerable.Reverse(bytes).ToArray();
+//// }
- return new BigInteger(bytes);
- }
- }
-}
-
-#endif
+//// // A leading bit of 1 defines a negative number. If the input should be interpreted as unsigned, prepend a new zero byte, if there’s a leading 1.
+//// // As bytes is in little endian order, check the most significant bit of the last byte. If it is 1, append the zero byte.
+//// if (isUnsigned && bytes.Length > 0 && (bytes.Last() & 0x80) != 0)
+//// {
+////#if NET462
+//// var len = bytes.Length;
+//// var bytes2 = new byte[len + 1];
+//// bytes.CopyTo(bytes2, 0);
+//// bytes2[len] = 0;
+//// bytes = bytes2;
+////#else
+//// bytes = bytes.Append((byte)0).ToArray();
+////#endif
+//// }
+//// return new BigInteger(bytes);
+//// }
+//// }
+////}
+////#endif
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DoubleUtil.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DoubleUtil.cs
index 16b338c8..56580429 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DoubleUtil.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DoubleUtil.cs
@@ -124,10 +124,10 @@ public static bool IsNaN(double value)
{
var t = new NanUnion { DoubleValue = value };
- ulong exp = t.UintValue & 0xfff0000000000000;
- ulong man = t.UintValue & 0x000fffffffffffff;
+ ulong exp = t.UintValue & 0xfff0_0000_0000_0000;
+ ulong man = t.UintValue & 0x000f_ffff_ffff_ffff;
- return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
+ return exp is 0x7ff0_0000_0000_0000 or 0xfff0_0000_0000_0000 && man != 0;
}
///
@@ -166,10 +166,10 @@ public static int DoubleToInt(double value)
struct NanUnion
{
[FieldOffset(0)]
- internal double DoubleValue;
+ public double DoubleValue;
[FieldOffset(0)]
- internal readonly ulong UintValue;
+ public readonly ulong UintValue;
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/ErrorHelpers.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/ErrorHelpers.cs
index 471812bd..aa36af50 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/ErrorHelpers.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/ErrorHelpers.cs
@@ -13,7 +13,8 @@ namespace PdfSharp.Internal
static class TH
{
const string SendUsTheFile = "\nPDFsharp cannot read this PDF file. " +
- "If you think your file is a valid PDF file please send it to us so that we can fix this bug in the PDF parser.";
+ "If you think your file is a valid PDF file please send it to us so that we can fix this bug in the PDF parser. "+
+ "See " + UrlLiterals.LinkToCannotOpenPdfFile;
public static InvalidOperationException InvalidOperationException_CouldNotFindMetadataDictionary() =>
new("Could not find document’s metadata dictionary." + SendUsTheFile);
@@ -23,9 +24,11 @@ public static InvalidOperationException InvalidOperationException_ReferenceMustN
#region Reader Messages
- public static ObjectNotAvailableException ObjectNotAvailableException_CannotRetrieveStreamLength(Exception? innerException = null)
+ public static ObjectNotAvailableException ObjectNotAvailableException_CannotRetrieveStreamLength(SizeType? streamStart, Exception? innerException = null)
{
- const string message = "Cannot retrieve stream length." + SendUsTheFile;
+ string message = "Cannot retrieve stream length." +
+ (streamStart.HasValue ? $" Stream begins at {streamStart.Value}." : null) +
+ SendUsTheFile;
return innerException != null ? new(message, innerException) : new(message);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs
index 8964d121..8185fb27 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs
@@ -1,70 +1,70 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using Microsoft.Extensions.Logging;
-using PdfSharp.Logging;
-
-namespace PdfSharp.Internal
-{
- ///
- /// Static locking functions to make PDFsharp thread save.
- /// POSSIBLE BUG_OLD: Having more than one lock can lead to a deadlock.
- ///
- static class Lock
- {
- public static void EnterGdiPlus()
- {
-
- Monitor.Enter(GdiPlusLock);
- //Interlocked.Increment(ref _gdiPlusLockCount);
- _gdiPlusLockCount++; // Is atomic because we have the lock.
-
- if (IsFontFactoryLockedByCurrentThread)
- {
- // This should not happen by design.
- PdfSharpLogHost.Logger.LogCritical("Entered GDI+ lock while FontFactory lock is also taken.");
- }
- }
-
- public static void ExitGdiPlus()
- {
- Interlocked.Decrement(ref _gdiPlusLockCount);
- Monitor.Exit(GdiPlusLock);
- }
-
- public static bool IsGdiPlusLockedByCurrentThread => Monitor.IsEntered(GdiPlusLock);
-
- public static bool IsGdiPlusLookTaken() => _gdiPlusLockCount > 0;
-
-
- static readonly object GdiPlusLock = new();
- static int _gdiPlusLockCount;
-
- // ------------------------------------------------------------
-
- public static void EnterFontFactory()
- {
- Monitor.Enter(FontFactoryLock);
- _fontFactoryLockCount++; // Is atomic because we have the lock.
-
- if (IsGdiPlusLockedByCurrentThread)
- {
- // This should not happen by design.
- PdfSharpLogHost.Logger.LogCritical("Entered FontFactory lock while GDI+ lock is also taken.");
- }
- }
-
- public static void ExitFontFactory()
- {
- _fontFactoryLockCount--;
- Monitor.Exit(FontFactoryLock);
- }
-
- public static bool IsFontFactoryLockedByCurrentThread => Monitor.IsEntered(FontFactoryLock);
-
- public static bool IsFontFactoryLookTaken() => _fontFactoryLockCount > 0;
-
- static readonly object FontFactoryLock = new();
- static int _fontFactoryLockCount;
- }
-}
+////// PDFsharp - A .NET library for processing PDF
+////// See the LICENSE file in the solution root for more information.
+
+////using Microsoft.Extensions.Logging;
+////using PdfSharp.Logging;
+
+////namespace PdfSharp.Internal
+////{
+//// ///
+//// /// Static locking functions to make PDFsharp thread save.
+//// /// POSSIBLE BUG_OLD: Having more than one lock can lead to a deadlock.
+//// ///
+//// static class Locks // Renamed from Lock because of the new Lock class in .NET 9.
+//// {
+//// public static void EnterGdiPlus()
+//// {
+
+//// Monitor.Enter(GdiPlusLock);
+//// //Interlocked.Increment(ref _gdiPlusLockCount);
+//// _gdiPlusLockCount++; // Is atomic because we have the lock.
+
+//// if (IsFontFactoryLockedByCurrentThread)
+//// {
+//// // This should not happen by design.
+//// PdfSharpLogHost.Logger.LogCritical("Entered GDI+ lock while FontFactory lock is also taken.");
+//// }
+//// }
+
+//// public static void ExitGdiPlus()
+//// {
+//// Interlocked.Decrement(ref _gdiPlusLockCount);
+//// Monitor.Exit(GdiPlusLock);
+//// }
+
+//// public static bool IsGdiPlusLockedByCurrentThread => Monitor.IsEntered(GdiPlusLock);
+
+//// public static bool IsGdiPlusLookTaken() => _gdiPlusLockCount > 0;
+
+
+//// static readonly object GdiPlusLock = new();
+//// static int _gdiPlusLockCount;
+
+//// // ------------------------------------------------------------
+
+//// public static void EnterFontManagement()
+//// {
+//// Monitor.Enter(FontFactoryLock);
+//// _fontFactoryLockCount++; // Is atomic because we have the lock.
+
+//// if (IsGdiPlusLockedByCurrentThread)
+//// {
+//// // This should not happen by design.
+//// PdfSharpLogHost.Logger.LogCritical("Entered FontFactory lock while GDI+ lock is also taken.");
+//// }
+//// }
+
+//// public static void ExitFontManagement()
+//// {
+//// _fontFactoryLockCount--;
+//// Monitor.Exit(FontFactoryLock);
+//// }
+
+//// public static bool IsFontFactoryLockedByCurrentThread => Monitor.IsEntered(FontFactoryLock);
+
+//// public static bool IsFontFactoryLookTaken() => _fontFactoryLockCount > 0;
+
+//// static readonly object FontFactoryLock = new();
+//// static int _fontFactoryLockCount;
+//// }
+////}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Globals.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/PsGlobals.cs
similarity index 61%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Globals.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Internal/PsGlobals.cs
index 18b6999d..9f8bbfe4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Globals.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/PsGlobals.cs
@@ -2,23 +2,25 @@
// See the LICENSE file in the solution root for more information.
using Microsoft.Extensions.Logging;
+using PdfSharp.Fonts;
+using PdfSharp.Internal.OpenType;
using PdfSharp.Logging;
namespace PdfSharp.Internal
{
///
- /// The one and only class that hold all PDFsharp global stuff.
+ /// The one and only class that holds all PDFsharp global stuff.
///
- partial class Globals
+ partial class PsGlobals
{
- Globals()
+ PsGlobals()
{
- _version = _globalVersionCount++;
+ IncrementVersion();
}
- public static Globals Global => _global;
+ public static PsGlobals Global => _global;
- internal void RecreateGlobals()
+ internal static void RecreatePsGlobals()
{
_global = new();
}
@@ -29,10 +31,9 @@ internal void RecreateGlobals()
/// Gets the version of this instance.
///
public int Version => _version;
- /*readonly*/
int _version;
- public readonly FontStorage Fonts = new();
+ public readonly PsFontStorage Fonts = new();
///
/// The global version count gives every new instance of Globals a new unique
@@ -42,15 +43,22 @@ internal void RecreateGlobals()
///
/// The container of all global stuff in PDFsharp.
+ /// By creating a new instance of Globals all caches are reset
+ /// as if PDFsharp starts in a new process.
///
- static Globals _global = new();
+ static PsGlobals _global = new();
- public partial class FontStorage
+ public partial class PsFontStorage
{
// A partial class within a partial class 😨
- internal FontStorage()
+ internal PsFontStorage()
{
+ FontSourceCache = new(this);
+ GlyphTypefaceCache = new(this);
+ FontDescriptorCache = new(this);
+ FontFamilyCache = new(this);
+
_globalFontStorageVersionCount++;
}
@@ -63,6 +71,11 @@ public void CheckVersion(int version)
}
}
+ public readonly PsFontSourceCache FontSourceCache;
+ public readonly PsGlyphTypefaceCache GlyphTypefaceCache;
+ public readonly PsFontDescriptorCache FontDescriptorCache;
+ public readonly PsFontFamilyCache FontFamilyCache;
+
///
/// Gets the version of this instance.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/TokenizerHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/TokenizerHelper.cs
index 122a030a..280adaf1 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/TokenizerHelper.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/TokenizerHelper.cs
@@ -31,7 +31,7 @@ void Initialize(string? str, char quoteChar, char separator)
_quoteChar = quoteChar;
_argSeparator = separator;
- // Skip any whitespace.
+ // Skip any white-space.
while (_charIndex < _strLen)
{
if (!Char.IsWhiteSpace(_str!, _charIndex))
@@ -110,7 +110,7 @@ public bool NextToken(bool allowQuotedToken, char separator)
int newTokenIndex = _charIndex;
int newTokenLength = 0;
- // Loop until hit end of string or hit a separator or whitespace.
+ // Loop until hit end of string or hit a separator or white-space.
while (_charIndex < _strLen)
{
currentChar = _str[_charIndex];
@@ -166,11 +166,11 @@ void ScanToNextToken(char separator)
{
char currentChar = _str![_charIndex];
- // Ensure that currentChar is a white space or separator.
+ // Ensure that currentChar is a white-space or separator.
if (currentChar != separator && !Char.IsWhiteSpace(currentChar))
throw new InvalidOperationException("ExtraDataEncountered"); //SR.Get(SRID.TokenizerHelperExtraDataEncountered, new object[0]));
- // Loop until a character that isn’t the separator or white space.
+ // Loop until a character that isn’t the separator or white-space.
int argSepCount = 0;
while (_charIndex < _strLen)
{
@@ -186,7 +186,7 @@ void ScanToNextToken(char separator)
}
else if (Char.IsWhiteSpace(currentChar))
{
- // Skip white space.
+ // Skip white-space.
++_charIndex;
}
else
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfSignatureField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfSignatureField.cs
deleted file mode 100644
index 740d272b..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfSignatureField.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using PdfSharp.Pdf.IO;
-using PdfSharp.Drawing;
-using PdfSharp.Pdf.Annotations;
-
-namespace PdfSharp.Pdf.AcroForms
-{
- ///
- /// Represents the signature field.
- ///
- public sealed class PdfSignatureField : PdfAcroField
- {
- ///
- /// Initializes a new instance of PdfSignatureField.
- ///
- internal PdfSignatureField(PdfDocument document)
- : base(document)
- {
- CustomAppearanceHandler = null!;
- }
-
- internal PdfSignatureField(PdfDictionary dict)
- : base(dict)
- {
- CustomAppearanceHandler = null!;
- }
-
- ///
- /// Handler that creates the visual representation of the digital signature in PDF.
- ///
- public IAnnotationAppearanceHandler CustomAppearanceHandler { get; internal set; }
-
- ///
- /// Creates the custom appearance form X object for the annotation that represents
- /// this acro form text field.
- ///
- void RenderCustomAppearance()
- {
- var rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect);
-
- var visible = rect.X1 + rect.X2 + rect.Y1 + rect.Y2 != 0;
-
- if (!visible)
- return;
-
- if (CustomAppearanceHandler == null)
- throw new Exception("AppearanceHandler is not set.");
-
- var form = new XForm(_document, rect.Size);
- var gfx = XGraphics.FromForm(form);
-
- CustomAppearanceHandler.DrawAppearance(gfx, rect.ToXRect());
-
- form.DrawingFinished();
-
- // Get existing or create new appearance dictionary
- if (Elements[PdfAnnotation.Keys.AP] is not PdfDictionary ap)
- {
- ap = new PdfDictionary(_document);
- Elements[PdfAnnotation.Keys.AP] = ap;
- }
-
- // Set XRef to normal state
- ap.Elements["/N"] = form.PdfForm.Reference;
-
- // PdfRenderer can be null.
- form.PdfRenderer?.Close();
- }
-
- internal override void PrepareForSave()
- {
- base.PrepareForSave();
- if (CustomAppearanceHandler != null!)
- RenderCustomAppearance();
- }
-
- ///
- /// Writes a key/value pair of this signature field dictionary.
- ///
- internal override void WriteDictionaryElement(PdfWriter writer, PdfName key)
- {
- // Don’t encrypt Contents key’s value (PDF Reference 2.0: 7.6.2, Page 71).
- if (key.Value == Keys.Contents)
- {
- var effectiveSecurityHandler = writer.EffectiveSecurityHandler;
- writer.EffectiveSecurityHandler = null;
- base.WriteDictionaryElement(writer, key);
- writer.EffectiveSecurityHandler = effectiveSecurityHandler;
- }
- else
- base.WriteDictionaryElement(writer, key);
- }
-
- ///
- /// Predefined keys of this dictionary.
- /// The description comes from PDF 1.4 Reference.
- ///
- public new class Keys : PdfAcroField.Keys
- {
- ///
- /// (Optional) The type of PDF object that this dictionary describes; if present,
- /// must be Sig for a signature dictionary.
- ///
- [KeyInfo(KeyType.Name | KeyType.Optional)]
- public const string Type = "/Type";
-
- ///
- /// (Required; inheritable) The name of the signature handler to be used for
- /// authenticating the field’s contents, such as Adobe.PPKLite, Entrust.PPKEF,
- /// CICI.SignIt, or VeriSign.PPKVS.
- ///
- [KeyInfo(KeyType.Name | KeyType.Required)]
- public const string Filter = "/Filter";
-
- ///
- /// (Optional) The name of a specific submethod of the specified handler.
- ///
- [KeyInfo(KeyType.Name | KeyType.Optional)]
- public const string SubFilter = "/SubFilter";
-
- ///
- /// (Required) An array of pairs of integers (starting byte offset, length in bytes)
- /// describing the exact byte range for the digest calculation. Multiple discontinuous
- /// byte ranges may be used to describe a digest that does not include the
- /// signature token itself.
- ///
- [KeyInfo(KeyType.Array | KeyType.Required)]
- public const string ByteRange = "/ByteRange";
-
- ///
- /// (Required) The encrypted signature token.
- ///
- [KeyInfo(KeyType.String | KeyType.Required)]
- public const string Contents = "/Contents";
-
- ///
- /// (Optional) The name of the person or authority signing the document.
- ///
- [KeyInfo(KeyType.TextString | KeyType.Optional)]
- public const string Name = "/Name";
-
- ///
- /// (Optional) The time of signing. Depending on the signature handler, this
- /// may be a normal unverified computer time or a time generated in a verifiable
- /// way from a secure time server.
- ///
- [KeyInfo(KeyType.Date | KeyType.Optional)]
- public const string M = "/M";
-
- ///
- /// (Optional) The CPU host name or physical location of the signing.
- ///
- [KeyInfo(KeyType.TextString | KeyType.Optional)]
- public const string Location = "/Location";
-
- ///
- /// (Optional) The reason for the signing, such as (I agree…).
- ///
- [KeyInfo(KeyType.TextString | KeyType.Optional)]
- public const string Reason = "/Reason";
-
- ///
- /// Gets the KeysMeta for these keys.
- ///
- internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
- static DictionaryMeta? _meta;
- }
-
- ///
- /// Gets the KeysMeta of this dictionary type.
- ///
- internal override DictionaryMeta Meta => Keys.Meta;
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfAction.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfAction.cs
index 7460e44c..c653480c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfAction.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfAction.cs
@@ -1,6 +1,8 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+// v7.0.0 Ready
+
namespace PdfSharp.Pdf.Actions
{
///
@@ -8,6 +10,8 @@ namespace PdfSharp.Pdf.Actions
///
public abstract class PdfAction : PdfDictionary
{
+ // Reference 2.0: 12.6 Actions / Page 506
+
///
/// Initializes a new instance of the class.
///
@@ -26,29 +30,40 @@ protected PdfAction(PdfDocument document)
Elements.SetName(Keys.Type, "/Action");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfAction(PdfDictionary dict)
+ : base(dict)
+ { }
+
///
/// Predefined keys of this dictionary.
///
internal class Keys : KeysBase
{
+ // Reference 2.0: Table 196 — Entries common to all action dictionaries / Page 506
+
///
/// (Optional) The type of PDF object that this dictionary describes;
- /// if present, must be Action for an action dictionary.
+ /// if present, shall be Action for an action dictionary.
///
[KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Action")]
public const string Type = "/Type";
///
- /// (Required) The type of action that this dictionary describes.
+ /// (Required) The type of action that this dictionary describes;
+ /// see PdfActionTypes for specific values.
///
[KeyInfo(KeyType.Name | KeyType.Required)]
public const string S = "/S";
///
- /// (Optional; PDF 1.2) The next action or sequence of actions to be performed
- /// after the action represented by this dictionary. The value is either a
- /// single action dictionary or an array of action dictionaries to be performed
- /// in order; see below for further discussion.
+ /// (Optional; PDF 1.2) The next action or sequence of actions that shall be performed after
+ /// the action represented by this dictionary. The value is either a single action dictionary
+ /// or an array of action dictionaries that shall be performed in order; see Note 1 for
+ /// further discussion.
///
[KeyInfo(KeyType.ArrayOrDictionary | KeyType.Optional)]
public const string Next = "/Next";
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfEmbeddedGoToAction.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfEmbeddedGoToAction.cs
index 0a7066b6..f29bdff7 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfEmbeddedGoToAction.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfEmbeddedGoToAction.cs
@@ -1,10 +1,12 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
-using System.Diagnostics;
using PdfSharp.Pdf.IO;
+// v7.0.0 TODO
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
namespace PdfSharp.Pdf.Actions
{
///
@@ -30,6 +32,16 @@ public PdfEmbeddedGoToAction(PdfDocument document)
Inititalize();
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfEmbeddedGoToAction(PdfDictionary dict)
+ : base(dict)
+ {
+ Inititalize();
+ }
+
///
/// Creates a link to an embedded document.
///
@@ -127,7 +139,7 @@ void ParseDestinationName()
/// Separator for splitting destination path segments ans destination name.
///
public const char Separator = '\\';
-
+
///
/// Path segment string used to move to the parent document.
///
@@ -155,7 +167,8 @@ void ParseDestinationName()
///
/// (Required) The destination in the target to jump to (see Section 8.2.1, “Destinations”).
///
- [KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)]
+ //[KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)]
+ [KeyInfo(KeyType.NameOrByteStringOrArray | KeyType.Required)] // #US373
public const string D = "/D";
///
@@ -175,77 +188,85 @@ void ParseDestinationName()
[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
public const string T = "/T";
}
+ }
- internal class TargetDictionary : PdfDictionary
+ public class TargetDictionary : PdfDictionary
+ {
+ TargetDictionary()
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal TargetDictionary(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ public static TargetDictionary CreateTargetChild(string name)
{
- TargetDictionary()
- { }
+ var target = new TargetDictionary();
- public static TargetDictionary CreateTargetChild(string name)
- {
- var target = new TargetDictionary();
+ target.Elements.SetName(Keys.R, "/C");
+ target.Elements.SetString(Keys.N, name);
- target.Elements.SetName(Keys.R, "/C");
- target.Elements.SetString(Keys.N, name);
+ return target;
+ }
- return target;
- }
+ public static TargetDictionary CreateTargetParent()
+ {
+ var target = new TargetDictionary();
- public static TargetDictionary CreateTargetParent()
- {
- var target = new TargetDictionary();
+ target.Elements.SetName(Keys.R, "/P");
- target.Elements.SetName(Keys.R, "/P");
+ return target;
+ }
- return target;
- }
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ internal class Keys : KeysBase
+ {
+ ///
+ /// (Required) Specifies the relationship between the current document and the target
+ /// (which may be an intermediate target). Valid values are P (the target is the parent
+ /// of the current document) and C (the target is a child of the current document).
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Required)]
+ public const string R = "/R";
///
- /// Predefined keys of this dictionary.
+ /// (Required if the value of R is C and the target is located in the EmbeddedFiles name tree;
+ /// otherwise, it must be absent) The name of the file in the EmbeddedFiles name tree.
///
- internal class Keys : KeysBase
- {
- ///
- /// (Required) Specifies the relationship between the current document and the target
- /// (which may be an intermediate target). Valid values are P (the target is the parent
- /// of the current document) and C (the target is a child of the current document).
- ///
- [KeyInfo(KeyType.Name | KeyType.Required)]
- public const string R = "/R";
-
- ///
- /// (Required if the value of R is C and the target is located in the EmbeddedFiles name tree;
- /// otherwise, it must be absent) The name of the file in the EmbeddedFiles name tree.
- ///
- [KeyInfo(KeyType.ByteString | KeyType.Optional)]
- public const string N = "/N";
-
- /////
- ///// (Required if the value of R is C and the target is associated with a file attachment annotation;
- ///// otherwise, it must be absent) If the value is an integer, it specifies the page number (zero-based)
- ///// in the current document containing the file attachment annotation. If the value is a string,
- ///// it specifies a named destination in the current document that provides the page number of the
- ///// file attachment annotation.
- /////
- //[KeyInfo(KeyType.Integer | KeyType.ByteString | KeyType.Optional)]
- //public const string P = "/P";
-
- /////
- ///// (Required if the value of R is C and the target is associated with a file attachment annotation;
- ///// otherwise, it must be absent) If the value is an integer, it specifies the index (zero-based)
- ///// of the annotation in the Annots array (see Table 3.27) of the page specified by P. If the value
- ///// is a text string, it specifies the value of NM in the annotation dictionary (see Table 8.15).
- /////
- //[KeyInfo(KeyType.Integer | KeyType.TextString | KeyType.Optional)]
- //public const string A = "/A";
-
- ///
- /// (Optional) A target dictionary specifying additional path information to the target document.
- /// If this entry is absent, the current document is the target file containing the destination.
- ///
- [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
- public const string T = "/T";
- }
+ [KeyInfo(KeyType.ByteString | KeyType.Optional)]
+ public const string N = "/N";
+
+ /////
+ ///// (Required if the value of R is C and the target is associated with a file attachment annotation;
+ ///// otherwise, it must be absent) If the value is an integer, it specifies the page number (zero-based)
+ ///// in the current document containing the file attachment annotation. If the value is a string,
+ ///// it specifies a named destination in the current document that provides the page number of the
+ ///// file attachment annotation.
+ /////
+ //[KeyInfoBug(KeyType.Integer | KeyType.ByteString | KeyType.Optional)] // #US373 Cannot "|" types.
+ //public const string P = "/P";
+
+ /////
+ ///// (Required if the value of R is C and the target is associated with a file attachment annotation;
+ ///// otherwise, it must be absent) If the value is an integer, it specifies the index (zero-based)
+ ///// of the annotation in the Annots array (see Table 3.27) of the page specified by P. If the value
+ ///// is a text string, it specifies the value of NM in the annotation dictionary (see Table 8.15).
+ /////
+ //[KeyInfoBug(KeyType.Integer | KeyType.TextString | KeyType.Optional)] // #US373 Cannot "|" types.
+ //public const string A = "/A";
+
+ ///
+ /// (Optional) A target dictionary specifying additional path information to the target document.
+ /// If this entry is absent, the current document is the target file containing the destination.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string T = "/T";
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfGoToAction.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfGoToAction.cs
index 1f2560c5..6bab204b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfGoToAction.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfGoToAction.cs
@@ -3,6 +3,8 @@
using PdfSharp.Pdf.IO;
+// v7.0.0 REVIEW
+
namespace PdfSharp.Pdf.Actions
{
///
@@ -10,6 +12,8 @@ namespace PdfSharp.Pdf.Actions
///
public sealed class PdfGoToAction : PdfAction
{
+ // Reference 2.0: 12.6.4.2 Go-To actions / Page 511
+
///
/// Initializes a new instance of the class.
///
@@ -28,6 +32,14 @@ public PdfGoToAction(PdfDocument document)
Inititalize();
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfGoToAction(PdfDictionary dict)
+ : base(dict)
+ { }
+
///
/// Creates a link within the current document.
///
@@ -44,8 +56,7 @@ public static PdfGoToAction CreateGoToAction(string destinationName)
void Inititalize()
{
- Elements.SetName(PdfAction.Keys.Type, "/Action");
- Elements.SetName(PdfAction.Keys.S, "/GoTo");
+ Elements.SetName(PdfAction.Keys.S, PdfNamedActionTypes.GoTo);
}
internal override void WriteObject(PdfWriter writer)
@@ -60,9 +71,11 @@ internal override void WriteObject(PdfWriter writer)
///
internal new class Keys : PdfAction.Keys
{
+ // Reference 2.0: Table 202 — Additional entries specific to a go-to action / Page 511
+
/////
///// (Required) The type of action that this dictionary describes;
- ///// must be GoTo for a go-to action.
+ ///// shall be GoTo for a go-to action.
/////
//[KeyInfo(KeyType.Name | KeyType.Required, FixedValue = "GoTo")]
//public const string S = "/S";
@@ -70,8 +83,15 @@ internal override void WriteObject(PdfWriter writer)
///
/// (Required) The destination to jump to (see Section 8.2.1, “Destinations”).
///
- [KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)]
+ //[KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)] // #US373 Cannot "|" types.
+ [KeyInfo(KeyType.NameOrByteStringOrArray | KeyType.Required)]
public const string D = "/D";
+
+ ///
+ /// (Required) The destination to jump to (see Section 8.2.1, “Destinations”).
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string SD = "/SD";
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfRemoteGoToAction.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfRemoteGoToAction.cs
index 308c7213..b62ca504 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfRemoteGoToAction.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfRemoteGoToAction.cs
@@ -3,6 +3,8 @@
using PdfSharp.Pdf.IO;
+// v7.0.0 TODO
+
namespace PdfSharp.Pdf.Actions
{
///
@@ -28,6 +30,16 @@ public PdfRemoteGoToAction(PdfDocument document)
Inititalize();
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfRemoteGoToAction(PdfDictionary dict)
+ : base(dict)
+ {
+ Inititalize();
+ }
+
///
/// Creates a link to another document.
///
@@ -90,7 +102,8 @@ string EncodePath(string path)
///
/// (Required) The destination to jump to (see Section 8.5.3, “Action Types”).
///
- [KeyInfo(KeyType.String | KeyType.Dictionary | KeyType.Required)]
+ //[KeyInfo(KeyType.String | KeyType.Dictionary | KeyType.Required)] // #US373 Cannot "|" types.
+ [KeyInfo(KeyType.StringOrDictionary | KeyType.Required)] // #US373 Cannot "|" types.
//[KeyInfo(KeyType.FileSpecification | KeyType.Required)] // File Specifications are not yet implemented.
public const string F = "/F";
@@ -100,7 +113,8 @@ string EncodePath(string path)
/// its first element must be a page number within the remote document rather than an indirect reference to a page object
/// in the current document. The first page is numbered 0.
///
- [KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)]
+ //[KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)] // #US373 Cannot "|" types.
+ [KeyInfo(KeyType.NameOrByteStringOrArray | KeyType.Required)] // #US373 Cannot "|" types.
public const string D = "/D";
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionNames.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionNames.cs
index bb9d0b76..d147d011 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionNames.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionNames.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
namespace PdfSharp.Pdf.Actions
@@ -28,4 +28,4 @@ public enum PdfNamedActionNames
///
LastPage
}
-}
\ No newline at end of file
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionTypes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionTypes.cs
new file mode 100644
index 00000000..4fa8b595
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionTypes.cs
@@ -0,0 +1,116 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// v7.0.0 Ready
+
+namespace PdfSharp.Pdf.Actions
+{
+ ///
+ /// Specifies the action types.
+ ///
+ public enum PdfNamedActionTypes
+ {
+ // Reference 2.0: 12.6.4 Action types / Page 510
+ // Reference 2.0: Table 201 — Action types / Page 510
+
+ ///
+ /// Go to a destination in the current document.
+ ///
+ GoTo = 1,
+
+ ///
+ /// ("Go-to remote") Go to a destination in another document.
+ ///
+ GoToR,
+
+ ///
+ /// ("Go-to embedded"; PDF 1.6) Go to a destination in an embedded file.
+ ///
+ GoToE,
+
+ ///
+ /// ("Go-to document part"; PDF 2.0) Go to a specified DPart in the current document.
+ ///
+ GoToDp,
+
+ ///
+ /// Launch an application, usually to open a file.
+ ///
+ Launch,
+
+ ///
+ /// Begin reading an article thread.
+ ///
+ Thread,
+
+ ///
+ /// Resolve a uniform resource identifier.
+ ///
+ URI,
+
+ ///
+ /// (PDF 1.2; deprecated in PDF 2.0) Play a sound.
+ ///
+ Sound,
+
+ ///
+ /// (PDF 1.2; deprecated in PDF 2.0) Play a movie.
+ ///
+ Movie,
+
+ ///
+ /// (PDF 1.2) Set an annotation’s Hidden flag.
+ ///
+ Hide,
+
+ ///
+ /// (PDF 1.2) Execute a predefined action.
+ ///
+ Named,
+
+ ///
+ /// (PDF 1.2) Send data to a uniform resource locator.
+ ///
+ SubmitForm,
+
+ ///
+ /// (PDF 1.2) Set fields to their default values.
+ ///
+ ResetForm,
+
+ ///
+ /// (PDF 1.2) Import field values from a file.
+ ///
+ ImportData,
+
+ ///
+ /// (PDF 1.5) Set the states of optional content groups.
+ ///
+ SetOCGState,
+
+ ///
+ /// (PDF 1.5) Controls the playing of multimedia content.
+ ///
+ Rendition,
+
+ ///
+ /// (PDF 1.5) Updates the display of a document, using a transition dictionary.
+ ///
+ Trans,
+
+ ///
+ /// (PDF 1.6) Set the current view of a 3D annotation.
+ ///
+ GoTo3DView,
+
+ ///
+ /// (PDF 1.3) Execute an ECMAScript script.
+ ///
+ JavaScript,
+
+ ///
+ /// (PDF 2.0; RichMedia annotation only) Specifies a command to be sent to the annotation’s handler.
+ ///
+ RichMediaExecute,
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs
index 47ce5941..f7479828 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs
@@ -1,9 +1,7 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using PdfSharp.Drawing;
-using PdfSharp.Pdf.Filters;
-using PdfSharp.Fonts.OpenType;
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Advanced
{
@@ -13,7 +11,7 @@ namespace PdfSharp.Pdf.Advanced
/// PDFsharp only used CIDFontType2 which is a TrueType font program.
///
// ReSharper disable once InconsistentNaming
- class PdfCIDFont : PdfFont
+ public class PdfCIDFont : PdfFont
{
public PdfCIDFont(PdfDocument document)
: base(document)
@@ -31,12 +29,12 @@ public PdfCIDFont(PdfDocument document, PdfFontDescriptor fontDescriptor /*, XFo
cid.Elements.SetInteger("/Supplement", 0);
Elements.SetValue(Keys.CIDSystemInfo, cid);
// #PDF-UA: 'Identity' or a stream must obviously be set for CIDFonts to satisfy PDF/UA requirements.
- Elements.SetName(Keys.CIDToGIDMap, "Identity");
+ Elements.SetName(Keys.CIDToGIDMap, "/Identity");
FontDescriptor = fontDescriptor;
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
Owner.IrefTable.TryAdd(fontDescriptor);
- Elements[Keys.FontDescriptor] = fontDescriptor.Reference;
+ Elements[Keys.FontDescriptor] = fontDescriptor.RequiredReference;
//FontEncoding = font.PdfOptions.FontEncoding;
FontEncoding = PdfFontEncoding.Unicode;
@@ -59,11 +57,19 @@ public PdfCIDFont(PdfDocument document, PdfFontDescriptor fontDescriptor, byte[]
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
//Owner.IrefTable.Add(fontDescriptor);
Owner.IrefTable.TryAdd(fontDescriptor);
- Elements[Keys.FontDescriptor] = fontDescriptor.Reference;
+ Elements[Keys.FontDescriptor] = fontDescriptor.RequiredReference;
FontEncoding = PdfFontEncoding.Unicode;
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfCIDFont(PdfDictionary dict)
+ : base(dict)
+ { }
+
public string BaseFont
{
get => Elements.GetName(Keys.BaseFont);
@@ -175,7 +181,6 @@ internal override void PrepareForSave()
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCatalog.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCatalog.cs
index 7231ef8e..1b6b3ad3 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCatalog.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCatalog.cs
@@ -2,16 +2,21 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Pdf.IO;
-using PdfSharp.Pdf.AcroForms;
+using PdfSharp.Pdf.Forms;
+using PdfSharp.Pdf.Metadata;
using PdfSharp.Pdf.Structure;
+// v7.0.0 TODO review and cleanup
+
namespace PdfSharp.Pdf.Advanced
{
///
- /// Represents the catalog dictionary.
+ /// Represents the PDF catalog dictionary.
///
public sealed class PdfCatalog : PdfDictionary
{
+ // Reference 2.0: / Page
+
///
/// Initializes a new instance of the class.
///
@@ -21,6 +26,10 @@ public PdfCatalog(PdfDocument document)
Elements.SetName(Keys.Type, "/Catalog");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfCatalog(PdfDictionary dictionary)
: base(dictionary)
{ }
@@ -33,6 +42,7 @@ public string Version
get => _version;
set
{
+ // TODO Create: public enum PdfVersion { Pdf10, Pdf11, ..., Pdf20}
switch (value)
{
case "1.0":
@@ -49,33 +59,23 @@ public string Version
case "1.6":
throw new InvalidOperationException("Unsupported PDF version.");
+ case "1.7":
+ case "2.0":
+ _version = value;
+ break;
+
default:
throw new ArgumentException("Invalid version.");
}
}
}
- string _version = "1.4";
+ string _version = "1.7";
///
- /// Gets the pages collection of this document.
+ /// Gets the page tree root of this document. // TODO flat or not
///
public PdfPages Pages
- {
- get
- {
- if (_pages == null)
- {
- _pages = (PdfPages?)Elements.GetValue(Keys.Pages, VCF.CreateIndirect) ?? NRT.ThrowOnNull();
- if (Owner.IsImported)
- {
- _pages.FlattenPageTree();
- //foreach (var page in _pages)
- // page.InitPageSize();
- }
- }
- return _pages;
- }
- }
+ => _pages ??= Elements.GetRequiredDictionary(Keys.Pages, VCF.CreateIndirect);
PdfPages? _pages;
///
@@ -100,16 +100,13 @@ internal PdfPageMode PageMode
/// Implementation of PdfDocument.ViewerPreferences.
///
internal PdfViewerPreferences ViewerPreferences
- {
- get
- {
- if (_viewerPreferences == null)
- _viewerPreferences = (PdfViewerPreferences?)Elements.GetValue(Keys.ViewerPreferences, VCF.CreateIndirect) ??
- NRT.ThrowOnNull();
- return _viewerPreferences;
- }
- }
- PdfViewerPreferences? _viewerPreferences;
+ => Elements.GetRequiredDictionary(Keys.ViewerPreferences, VCF.CreateIndirect);
+
+ internal bool HasOutline
+ => Elements.GetDictionary(Keys.Outlines, VCF.NoTransform) != null;
+
+ internal PdfOutline Outline
+ => Elements.GetRequiredDictionary(Keys.Outlines, VCF.CreateIndirect);
///
/// Implementation of PdfDocument.Outlines.
@@ -118,82 +115,116 @@ internal PdfOutlineCollection Outlines
{
get
{
- if (_outline == null)
- {
- ////// Ensure that the page tree exists.
- ////// ReSharper disable once UnusedVariable because we need dummy to call the getter.
- ////PdfPages dummy = Pages;
-
- // Now create the outline item tree.
- _outline = (PdfOutline?)Elements.GetValue(Keys.Outlines, VCF.CreateIndirect) ?? NRT.ThrowOnNull();
- }
- return _outline.Outlines;
+ var outline = Elements.GetRequiredDictionary(Keys.Outlines, VCF.CreateIndirect);
+ return outline.Outlines;
}
}
- PdfOutline? _outline;
///
- /// Gets the name dictionary of this document.
+ /// Gets a value indicating whether the document has a /Metadata dictionary.
+ ///
+ public bool HasMetadata => Elements.GetDictionary(Keys.Metadata) != null;
+
+ ///
+ /// Gets the metadata dictionary of this document.
+ /// An empty dictionary is created if it does not exist.
+ ///
+ [Obsolete("Use GetMetadata or GetRequiredMetadata.")]
+ public PdfMetadata Metadata
+ => Elements.GetRequiredDictionary(Keys.Metadata, VCF.CreateIndirect);
+
+ ///
+ /// Gets the metadata dictionary of this document.
+ /// Returns null if it does not exist.
+ ///
+ public PdfMetadata? GetMetadata()
+ => Elements.GetDictionary(Keys.Metadata);
+
+ ///
+ /// Gets the metadata dictionary of this document.
+ /// It is created if it does not exist.
+ ///
+ public PdfMetadata GetOrCreateMetadata()
+ => Elements.GetRequiredDictionary(Keys.Metadata, VCF.CreateIndirect);
+
+ ///
+ /// Gets a value indicating whether the document has a /Names dictionary.
+ ///
+ public bool HasNames => Elements.GetDictionary(Keys.Names) != null;
+
+ ///
+ /// Gets the /Names dictionary of this document.
+ /// An empty dictionary is created if it does not exist.
///
public PdfNameDictionary Names
- {
- get
- {
- if (_names == null)
- {
- var dict = Elements.GetDictionary(Keys.Names);
- if (dict != null)
- _names = new PdfNameDictionary(dict);
- else
- {
- _names = new PdfNameDictionary(Owner);
- Owner.Internals.AddObject(_names);
- Elements.SetReference(Keys.Names, _names.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull());
- }
- }
- return _names;
- }
- }
- PdfNameDictionary? _names;
+ => Elements.GetRequiredDictionary(Keys.Names, VCF.CreateIndirect);
+
+ ///
+ /// Gets the /Names dictionary of this document.
+ /// Returns null if it does not exist.
+ ///
+ public PdfNameDictionary? GetNames()
+ => Elements.GetDictionary(Keys.Metadata);
///
- /// Gets the named destinations defined in the Catalog
+ /// Gets the /Names dictionary of this document.
+ /// It is created if it does not exist.
///
+ public PdfNameDictionary GetOrCreateNames()
+ => Elements.GetRequiredDictionary(Keys.Metadata, VCF.CreateIndirect);
+
+ ///
+ /// Gets a value indicating whether the document has a /Dests dictionary.
+ ///
+ public bool HasDestinations
+ => Elements.GetDictionary(Keys.Dests) != null;
+
+ ///
+ /// Gets the named destinations defined in the Catalog.
+ ///
+ [Obsolete("Use GetDestinations or GetRequiredDestinations.")]
public PdfNamedDestinations Destinations
- {
- get
- {
- if (_dests == null)
- {
- var dict = Elements.GetDictionary(Keys.Dests);
- if (dict != null)
- _dests = new PdfNamedDestinations(dict);
- else
- {
- _dests = new PdfNamedDestinations();
- _dests = new PdfNamedDestinations();
- Owner.Internals.AddObject(_dests);
- Elements.SetReference(Keys.Dests, _dests.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull());
- }
- }
- return _dests;
- }
- }
- PdfNamedDestinations? _dests;
+ => Elements.GetRequiredDictionary(Keys.Dests, VCF.CreateIndirect);
///
- /// Gets the AcroForm dictionary of this document.
+ /// Gets the named destinations dictionary of this document.
+ /// Returns null if it does not exist.
///
- public PdfAcroForm AcroForm
- {
- get
- {
- if (_acroForm == null)
- _acroForm = (PdfAcroForm?)Elements.GetValue(Keys.AcroForm) ?? NRT.ThrowOnNull();
- return _acroForm;
- }
- }
- PdfAcroForm? _acroForm;
+ public PdfNamedDestinations? GetDestinations()
+ => Elements.GetDictionary(Keys.Dests);
+
+ ///
+ /// Gets the named destinations dictionary of this document.
+ /// It is created if it does not exist.
+ ///
+ public PdfNamedDestinations GetRequiredDestinations()
+ => Elements.GetRequiredDictionary(Keys.Dests, VCF.CreateIndirect);
+
+ ///
+ /// Gets a value indicating whether the document has a /AcroForm dictionary.
+ ///
+ public bool HasAcroForm => Elements.GetDictionary(Keys.AcroForm) != null;
+
+ ///
+ /// Gets the interactive form (AcroForm) dictionary of this document.
+ ///
+ [Obsolete("Use GetAcroForm or GetOrCreateAcroForm.")]
+ public PdfForm AcroForm
+ => Elements.GetRequiredDictionary(Keys.AcroForm, VCF.CreateIndirect);
+
+ ///
+ /// Gets the acro form dictionary of this document.
+ /// Returns null if it does not exist.
+ ///
+ public PdfForm? GetAcroForm()
+ => Elements.GetDictionary(Keys.AcroForm);
+
+ ///
+ /// Gets the acro form dictionary of this document.
+ /// It is created if it does not exist.
+ ///
+ public PdfForm GetOrCreateAcroForm()
+ => Elements.GetRequiredDictionary(Keys.AcroForm, VCF.CreateIndirect);
///
/// Gets or sets the language identifier specifying the natural language for all text in the document.
@@ -217,28 +248,49 @@ public string Language
internal override void PrepareForSave()
{
// Prepare pages.
- if (_pages != null)
- _pages.PrepareForSave();
+ _pages?.PrepareForSave();
+
+ // Prepare metadata.
+ var metadataManager = Document.GetMetadataManager();
+ metadataManager.PrepareForSave();
+
+ var xxx = metadataManager.ToString();
+
+ // Prepare AcroForm objects.
+ var acroForm = GetAcroForm();
+ var fields = acroForm?.Elements.GetArray(PdfForm.Keys.Fields);
+ if (fields != null)
+ {
+ foreach (var formRef in fields.Elements)
+ {
+ var form = (formRef as PdfReference)?.Value;
+ if (form is PdfFormField field)
+ {
+ field.PrepareForSave();
+ }
+ }
+ }
// Create outline objects.
- //if (_outline != null && _outline.Outlines.Count > 0)
- if (_outline is { Outlines.Count: > 0 })
+ if (HasOutline && Outline is { Outlines.Count: > 0 } outline)
{
- if (Elements[Keys.PageMode] == null)
+ if (!Elements.HasValue(Keys.PageMode)) // #US373 GetValue() not needed, just checking existence.
PageMode = PdfPageMode.UseOutlines;
- _outline.PrepareForSave();
+ outline.PrepareForSave();
}
// Clean up structure tree root.
- if (Elements.GetObject(Keys.StructTreeRoot) is PdfStructureTreeRoot str)
- str.PrepareForSave();
+ if (Elements.GetObject(Keys.StructTreeRoot) is PdfStructureTreeRoot structTree)
+ structTree.PrepareForSave();
}
internal override void WriteObject(PdfWriter writer)
{
- if (_outline != null && _outline.Outlines.Count > 0)
+ var state = Document.State;
+ //if (_outline != null && _outline.Outlines.Count > 0)
+ if (HasOutline && Outline is { Outlines.Count: > 0 })
{
- if (Elements[Keys.PageMode] == null)
+ if (!Elements.HasValue(Keys.PageMode)) // #US373 GetValue() not needed, just checking existence.
PageMode = PdfPageMode.UseOutlines;
}
base.WriteObject(writer);
@@ -247,7 +299,7 @@ internal override void WriteObject(PdfWriter writer)
///
/// Predefined keys of this dictionary.
///
- internal sealed class Keys : KeysBase
+ public sealed class Keys : KeysBase
{
// ReSharper disable InconsistentNaming
@@ -286,6 +338,8 @@ internal sealed class Keys : KeysBase
///
/// (Optional; PDF 1.2) The document’s name dictionary.
+ /// (PDF 2.0) For unencrypted wrapper documents for an encrypted payload document
+ /// the Names dictionary is required and shall contain the EmbeddedFiles name tree.
///
[KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)]
public const string Names = "/Names";
@@ -371,7 +425,7 @@ internal sealed class Keys : KeysBase
///
/// (Optional; PDF 1.2) The document’s interactive form (AcroForm) dictionary.
///
- [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional, typeof(PdfAcroForm))]
+ [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional, typeof(PdfForm))]
public const string AcroForm = "/AcroForm";
///
@@ -458,7 +512,7 @@ internal sealed class Keys : KeysBase
public const string Collection = "/Collection";
///
- /// (Optional; PDF 1.7) A flag used to expedite the display of PDF documents containing XFA forms.
+ /// Optional; deprecated in PDF 2.0) A flag used to expedite the display of PDF documents containing XFA forms.
/// It specifies whether the document must be regenerated when the document is first opened.
/// If true, the viewer application treats the document as a shell and regenerates the content
/// when the document is opened, regardless of any dynamic forms settings that appear in the XFA
@@ -468,15 +522,30 @@ internal sealed class Keys : KeysBase
/// See the XML Forms Architecture (XFA) Specification (Bibliography).
/// Default value: false.
///
- [KeyInfo("1.7", KeyType.Boolean | KeyType.Optional)]
+ [KeyInfo("1.7", KeyType.Boolean | KeyType.Optional | KeyType.DeprecatedIn20)]
public const string NeedsRendering = "/NeedsRendering";
+ // ----- New PDF 2.0 entries -----
+
+ // DSS - nyi
+
+ ///
+ /// (Optional; PDF 2.0) An array of one or more file specification dictionaries which denote the associated
+ /// files for this PDF document.
+ /// For unencrypted wrapper documents for an encrypted payload document the AF key is required and shall
+ /// include a reference to the file specification dictionary for the encrypted payload document.
+ ///
+ [KeyInfo("2.0", KeyType.ArrayOfDictionaries | KeyType.Optional, ObjectType = typeof(PdfArrayOfDictionaries))]
+ public const string AF = "/AF";
+
+ // DPartRoot - nyi
+
// ReSharper restore InconsistentNaming
///
/// Gets the KeysMeta for these keys.
///
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs
index 902f72b8..248d7a95 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs
@@ -18,7 +18,7 @@ public sealed class PdfContent : PdfDictionary
/// Initializes a new instance of the class.
///
public PdfContent(PdfDocument document)
- : base(document)
+ : base(document, true)
{ }
///
@@ -31,10 +31,10 @@ internal PdfContent(PdfPage page)
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
- /// The dict.
- public PdfContent(PdfDictionary dict) // HACK_OLD PdfContent
+ internal PdfContent(PdfDictionary dict) // HACK_OLD PdfContent
: base(dict)
{
// A PdfContent dictionary is always unfiltered.
@@ -50,13 +50,13 @@ public bool Compressed
{
if (value)
{
- var filter = Elements["/Filter"];
+ var filter = Elements.GetValue(PdfStream.Keys.Filter); // #US373
if (filter == null && Stream is not null)
{
- byte[] bytes = Filtering.FlateDecode.Encode(Stream.Value, _document.Options.FlateEncodeMode);
+ byte[] bytes = Filtering.FlateDecode.Encode(Stream.Value, Document.Options.FlateEncodeMode);
Stream.Value = bytes;
- Elements.SetInteger("/Length", Stream.Length);
- Elements.SetName("/Filter", "/FlateDecode");
+ Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
+ Elements.SetInteger(PdfStream.Keys.Length, Stream.Length);
}
}
}
@@ -67,18 +67,18 @@ public bool Compressed
///
void Decode()
{
- if (Stream is { Value: not null })
+ if (Stream is not null)
{
- var item = Elements["/Filter"];
- if (item != null)
+ var filter = Elements.GetValue(PdfStream.Keys.Filter);
+ if (filter != null)
{
- var decodeParams = Elements[PdfStream.Keys.DecodeParms];
- var bytes = Filtering.Decode(Stream.Value, item, decodeParams);
+ var decodeParams = Elements.GetValue(PdfStream.Keys.DecodeParms);
+ var bytes = Filtering.Decode(Stream.Value, filter, decodeParams);
if (bytes != null!)
{
Stream.Value = bytes;
- Elements.Remove("/Filter");
- Elements.SetInteger("/Length", Stream.Length);
+ Elements.Remove(PdfStream.Keys.Filter);
+ Elements.SetInteger(PdfStream.Keys.Length, Stream.Length);
}
}
}
@@ -121,36 +121,57 @@ internal override void WriteObject(PdfWriter writer)
{
// GetContent also disposes the underlying XGraphics object, if one exists
//Stream = new PdfStream(PdfEncoders.RawEncoding.GetBytes(pdfRenderer.GetContent()), this);
- _pdfRenderer.Close();
+ if (_pdfRenderer is XGraphicsPdfRenderer xgfxRenderer)
+ {
+ xgfxRenderer.Close();
+ }
+ else
+ {
+ // Case: Renderer is a PDFsharp Graphics DrawingContext for PDF.
+ // No automatic close, throw.
+ throw new InvalidOperationException(
+ $"A renderer of type {_pdfRenderer.GetType().FullName} is still open for this content stream.");
+ }
Debug.Assert(_pdfRenderer == null);
}
if (Stream != null!)
{
- if (Owner.Options.CompressContentStreams && Elements.GetName("/Filter").Length == 0)
+ // Acrobat crashes if a PDF file contains an empty stream that is compressed and
+ // therefore about 2 to 4 bytes long. So we do not compress very short streams
+ // at all.
+ const int streamLengthCompressionThreshold = 32;
+
+ // Compress the stream if it has a minimum length and has no filter set yet.
+ // Short streams are smaller without compression.
+ if (Owner.Options.CompressContentStreams && !Elements.HasValue(PdfStream.Keys.Filter) &&
+ Stream.Value.Length > streamLengthCompressionThreshold)
{
- Stream.Value = Filtering.FlateDecode.Encode(Stream.Value, _document.Options.FlateEncodeMode);
- //Elements["/Filter"] = new PdfName("/FlateDecode");
- Elements.SetName("/Filter", "/FlateDecode");
+ Stream.Value = Filtering.FlateDecode.Encode(Stream.Value, Document.Options.FlateEncodeMode);
+ Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
}
- Elements.SetInteger("/Length", Stream.Length);
+ Elements.SetInteger(PdfStream.Keys.Length, Stream.Length);
}
base.WriteObject(writer);
}
- internal void SetRenderer(XGraphicsPdfRenderer? renderer) => _pdfRenderer = renderer;
- XGraphicsPdfRenderer? _pdfRenderer;
+ // Sets the renderer that currently renders this content stream.
+ internal void SetRenderer(IPageContentRenderer? renderer) => _pdfRenderer = renderer;
+
+ internal IPageContentRenderer? Renderer => _pdfRenderer;
+
+ IPageContentRenderer? _pdfRenderer;
///
/// Predefined keys of this dictionary.
///
- internal sealed class Keys : PdfStream.Keys
+ public sealed class Keys : PdfStream.Keys
{
///
/// Gets the KeysMeta for these keys.
///
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs
index 34583149..c418b01c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs
@@ -2,9 +2,10 @@
// See the LICENSE file in the solution root for more information.
using System.Collections;
+using PdfSharp.Internal;
using PdfSharp.Pdf.Content.Objects;
+using PdfSharp.Pdf.Internal;
using PdfSharp.Pdf.IO;
-using static PdfSharp.Pdf.PdfDictionary;
namespace PdfSharp.Pdf.Advanced
{
@@ -18,7 +19,7 @@ public sealed class PdfContents : PdfArray
///
/// The document.
public PdfContents(PdfDocument document)
- : base(document)
+ : base(document, false)
{ }
internal PdfContents(PdfArray array)
@@ -51,7 +52,6 @@ public PdfContent AppendContent()
SetModified();
PdfContent content = new PdfContent(Owner);
- Owner.IrefTable.Add(content);
Debug.Assert(content.Reference != null);
Elements.Add(content.Reference);
return content;
@@ -79,19 +79,17 @@ public PdfContent PrependContent()
public PdfContent CreateSingleContent()
{
byte[] bytes = [];
- byte[] bytes1;
- byte[] bytes2;
foreach (PdfItem iref in Elements)
{
PdfDictionary cont = (PdfDictionary)((PdfReference)iref).Value;
- bytes1 = bytes;
- bytes2 = cont.Stream!.UnfilteredValue;
+ var bytes1 = bytes;
+ var bytes2 = cont.Stream!.UnfilteredValue;
bytes = new byte[bytes1.Length + bytes2.Length + 1];
bytes1.CopyTo(bytes, 0);
bytes[bytes1.Length] = (byte)'\n';
bytes2.CopyTo(bytes, bytes1.Length + 1);
}
- PdfContent content = new PdfContent(Owner);
+ var content = new PdfContent(Owner);
content.Stream = new PdfDictionary.PdfStream(bytes, content);
return content;
}
@@ -99,28 +97,27 @@ public PdfContent CreateSingleContent()
///
/// Replaces the current content of the page with the specified content sequence.
///
- public PdfContent ReplaceContent(CSequence cseq)
+ public PdfContent ReplaceContent(CSequence seq)
{
- if (cseq == null)
- throw new ArgumentNullException(nameof(cseq));
+ if (seq == null)
+ throw new ArgumentNullException(nameof(seq));
- return ReplaceContent(cseq.ToContent());
+ var bytes = PdfEncoders.RawEncoding.GetBytes(seq.ToContent());
+ return ReplaceContent(bytes);
}
///
/// Replaces the current content of the page with the specified bytes.
///
- PdfContent ReplaceContent(byte[] contentBytes)
+ public PdfContent ReplaceContent(byte[] contentBytes)
{
Debug.Assert(Owner != null);
- PdfContent content = new PdfContent(Owner);
-
+ var content = new PdfContent(Owner);
content.CreateStream(contentBytes);
- Owner.IrefTable.Add(content);
Elements.Clear();
- Elements.Add(content.ReferenceNotNull);
+ Elements.Add(content.RequiredReference);
return content;
}
@@ -143,7 +140,7 @@ void SetModified()
byte[] value;
int length;
PdfContent content = (PdfContent)((PdfReference)Elements[0]).Value;
- if (content != null && content.Stream != null)
+ if (content is { Stream: not null })
{
length = content.Stream.Length;
value = new byte[length + 2];
@@ -154,7 +151,7 @@ void SetModified()
content.Elements.SetInteger("/Length", length + 2);
}
content = (PdfContent)((PdfReference)Elements[count - 1]).Value;
- if (content != null && content.Stream != null)
+ if (content is { Stream: not null })
{
length = content.Stream.Length;
value = new byte[length + 3];
@@ -175,7 +172,10 @@ internal override void WriteObject(PdfWriter writer)
{
// Save two bytes in PDF stream...
if (Elements.Count == 1)
+ {
+ var xxx = Elements[0];
Elements[0].WriteObject(writer);
+ }
else
base.WriteObject(writer);
}
@@ -222,7 +222,7 @@ public PdfContent Current
{
if (_index == -1 || _index >= _contents.Elements.Count)
throw new InvalidOperationException(PsMsgs.ListEnumCurrentOutOfRange);
- return _currentElement??throw new InvalidOperationException("Current called before MoveNext.");
+ return _currentElement ?? throw new InvalidOperationException("Current called before MoveNext.");
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceStream.cs
index 091f1197..867f31fd 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceStream.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceStream.cs
@@ -1,15 +1,14 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System.Collections.Generic;
-using System.Diagnostics;
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Advanced
{
///
/// Represents a PDF cross-reference stream.
///
- sealed class PdfCrossReferenceStream : PdfTrailer // Reference: 3.4.7 Cross-Reference Streams / Page 106
+ public sealed class PdfCrossReferenceStream : PdfTrailer // Reference: 3.4.7 Cross-Reference Streams / Page 106
{
///
/// Initializes a new instance of the class.
@@ -25,6 +24,14 @@ public PdfCrossReferenceStream(PdfDocument document)
#endif
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfCrossReferenceStream(PdfDictionary dict)
+ : base(dict)
+ { }
+
public readonly List Entries = new List();
public struct CrossReferenceStreamEntry
@@ -114,7 +121,7 @@ public struct CrossReferenceStreamEntry
///
/// Gets the KeysMeta for these keys.
///
- public new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs
index 327c212c..e15b6b0b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs
@@ -10,7 +10,9 @@
using System.Collections;
using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
using PdfSharp.Logging;
+using PdfSharp.Pdf.Forms;
using PdfSharp.Pdf.IO;
namespace PdfSharp.Pdf.Advanced
@@ -48,6 +50,15 @@ sealed class PdfCrossReferenceTable(PdfDocument document) // Must not be derived
///
public void Add(PdfReference iref)
{
+#if DEBUG_
+ //if (this is PdfObject { ObjectNumber: 96049 })
+ // _ = typeof(int);
+ if (iref.ObjectID.ObjectNumber == 96049)
+ {
+ _ = typeof(int);
+ var number = iref.ObjectNumber;
+ }
+#endif
if (iref.ObjectID.IsEmpty)
{
// When happens this?
@@ -69,6 +80,7 @@ public void Add(PdfReference iref)
$"This should not occur. If you think this is a bug in PDFsharp, please visit {UrlLiterals.LinkToCannotOpenPdfFile} for further information.", oldIref.ObjectID, oldIref.Position, iref.Position);
_objectTable.Remove(iref.ObjectID);
+ // TODO Set old object to dead.
}
_objectTable.Add(iref.ObjectID, iref);
@@ -78,51 +90,73 @@ public void Add(PdfReference iref)
///
/// Adds a PdfObject to the table.
+ /// This makes a direct object an indirect one.
///
- public void Add(PdfObject value)
+ public void Add(PdfObject obj)
{
+#if DEBUG
+ if (obj.ObjectID.ObjectNumber == 96049)
+ _ = typeof(int);
+#endif
+ if (obj == null)
+ throw new ArgumentNullException(nameof(obj));
+
+ if (obj.ParentInfo != null)
+ throw new InvalidOperationException(
+ "You cannot convert a PDF object to an indirect object when it was already used as a direct one.");
+
// ReSharper disable once NullableWarningSuppressionIsUsed
- if (value.Owner == null!)
+ if (obj.Owner == null!)
{
PdfSharpLogHost.PdfReadingLogger.LogWarning("Object without owner gets owned by the document it was added to.");
- value.Document = document;
+ obj.Document = document;
}
else
{
- Debug.Assert(value.Owner == document);
- if (value.Owner != document)
+ Debug.Assert(obj.Owner == document);
+ if (obj.Owner != document)
{
PdfSharpLogHost.PdfReadingLogger.LogError("Object not owned by the document it was added to.");
+ throw new InvalidOperationException("PDF object does not belong to this document.");
}
}
- if (value.ObjectID.IsEmpty)
+ if (obj.ObjectID.IsEmpty)
{
- // Create new object number.
- value.SetObjectID(GetNewObjectNumber(), 0);
+ // TODO: Check when this happens.
+ // Create new object number and create reference.
+ obj.SetObjectID(GetNewObjectNumber(), 0);
+#if DEBUG_
+ if (obj.ObjectNumber == 5)
+ _ = typeof(int);
+#endif
}
- if (_objectTable.ContainsKey(value.ObjectID))
+ if (_objectTable.ContainsKey(obj.ObjectID))
{
// This must not happen.
throw new InvalidOperationException("Object already in table.");
}
- _objectTable.Add(value.ObjectID, value.ReferenceNotNull);
+ _objectTable.Add(obj.ObjectID, obj.RequiredReference);
// Always adjust MaxObjectNumber when a new object is added.
- MaxObjectNumber = Math.Max(MaxObjectNumber, value.ObjectNumber);
+ MaxObjectNumber = Math.Max(MaxObjectNumber, obj.ObjectNumber);
}
///
/// Adds a PdfObject to the table if it was not already in.
/// Returns true if it was added, false otherwise.
///
- public bool TryAdd(PdfObject value)
+ public bool TryAdd(PdfObject obj)
{
- if (value.ObjectID.IsEmpty || !_objectTable.ContainsKey(value.ObjectID))
+#if DEBUG
+ if (obj.ObjectID.ObjectNumber == 96049)
+ _ = typeof(int);
+#endif
+ if (obj.ObjectID.IsEmpty || !_objectTable.ContainsKey(obj.ObjectID))
{
- Add(value);
+ Add(obj);
return true;
}
return false;
@@ -324,6 +358,13 @@ internal void Renumber()
iref.ObjectID = new PdfObjectID(idx + 1);
// Rehash with new number.
_objectTable.Add(iref.ObjectID, iref);
+
+ // Handle special case where a form field also is a widget annotation.
+ // We have to renumber the widget too.
+ if (iref.Value is PdfFormField { _widget: not null } formField)
+ {
+ formField._widget.Reference!.ObjectID = iref.ObjectID;
+ }
}
MaxObjectNumber = count;
//CheckConsistence();
@@ -581,6 +622,8 @@ void TransitiveClosureImplementation(Dictionary reference
// _ = typeof(int);
//}
#endif
+ if (pivot.IsDead)
+ Debugger.Break();
FindReferencedItems(pivot);
}
#if TEST_CODE
@@ -606,6 +649,11 @@ void FindReferencedItems(PdfObject pdfObj)
{
Debug.Assert(pdfObj is PdfDictionary or PdfArray, "Call with dictionary or array only.");
+#if DEBUG
+ if (pdfObj.ObjectNumber == 18)
+ _ = typeof(int);
+#endif
+
IEnumerable? items = null;
PdfDictionary? dict;
PdfArray? array;
@@ -618,8 +666,15 @@ void FindReferencedItems(PdfObject pdfObj)
foreach (PdfItem item in items)
{
+ if (item.IsDead)
+ Debugger.Break();
+
if (item is PdfReference iref)
{
+#if DEBUG
+ if (iref.ObjectNumber == 18)
+ _ = typeof(int);
+#endif
// Case: The item is an indirect object.
// Check if the reference belongs to the current document.
@@ -641,6 +696,29 @@ void FindReferencedItems(PdfObject pdfObj)
}
var newObject = iref.Value;
+ // Handle a rare case here. If an Acro field (interactive field) has only one
+ // child (one entry in /Kids array) that is a widget annotation it can merge
+ // the widget keys with its own keys.
+ // In this case PDFsharp creates a PdfArcoFieldWidget and a PdfWidgetAnnotation
+ // object that share the same elements container. Both have their own
+ // PdfReference, but only the reference of the PdfArcoFieldWidget is in the
+ // IRefTable, while PdfWidgetAnnotation has a reference not owned by its document.
+
+ // TODO: Fields with type (/FT) can also be widgets.
+
+ // It works, because the object IDs are the same for both references.
+ // So one PDF dictionary has two different .NET type. If you came from /Kits in
+ // PdfFormField, it is of type PdfArcoFieldWidget; if you came from /Annots in
+ // PdfPage, it is of type PdfWidgetAnnotation.
+ // The following code ensures that always the PdfReference of the PdfArcoFieldWidget
+ // is taken. This is the only use of the new internal virtual function ActualReference.
+ var newIref = newObject?.ActualReference ?? null; // TODO newObject can be null here.
+ if (newIref != null && !ReferenceEquals(iref, newIref))
+ {
+ if (references.ContainsKey(newIref))
+ continue;
+ iref = newIref;
+ }
// Ignore unreachable objects.
if (iref.Document != null)
@@ -657,15 +735,15 @@ void FindReferencedItems(PdfObject pdfObj)
Debug.Assert(ReferenceEquals(iref.Document, document));
if (newObject.ObjectID.ObjectNumber != 0)
references.Add(iref, null);
-#if TEST_CODE__
- // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because auf .NET Framework / Standard
+#if TEST_CODE_
+ // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because of .NET Framework / Standard
if (!doubleCheckReferences.ContainsKey(value))
doubleCheckReferences.Add(value, null);
else
_ = typeof(int);
#endif
- if (newObject is PdfDictionary or PdfArray)
+ if (newObject is PdfDictionary or PdfArray) // TODO PdfContainer
{
stack.Push(newObject);
#if TEST_CODE
@@ -682,7 +760,12 @@ void FindReferencedItems(PdfObject pdfObj)
else
{
// Case: The item is a direct object.
-
+#if DEBUG
+ // TODO: Now we have PdfContainer.
+ var f1 = item is PdfObject and (PdfDictionary or PdfArray);
+ var f2 = item is PdfContainer;
+ Debug.Assert(f1 == f2);
+#endif
if (item is PdfObject pdfDictionaryOrArray and (PdfDictionary or PdfArray))
{
#if TEST_CODE_
@@ -714,7 +797,7 @@ public PdfReference DeadObject
Add(_deadObject);
_deadObject.Elements.Add("/DeadObjectCount", new PdfInteger());
}
- return _deadObject.ReferenceNotNull;
+ return _deadObject.RequiredReference;
}
}
PdfDictionary? _deadObject;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDictionaryWithContentStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDictionaryWithContentStream.cs
index 51080774..0fcb31b2 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDictionaryWithContentStream.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDictionaryWithContentStream.cs
@@ -55,18 +55,19 @@ internal PdfResources Resources
internal string GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont)
{
- pdfFont = _document.FontTable.GetOrCreateFont(glyphTypeface, fontType);
+ pdfFont = Document.FontTable.GetOrCreateFont(glyphTypeface, fontType);
Debug.Assert(pdfFont != null);
string name = Resources.AddFont(pdfFont);
return name;
}
- string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont)
+ string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont)
=> GetFontName(glyphTypeface, fontType, out pdfFont);
internal string GetFontName(string idName, byte[] fontData, out PdfFont pdfFont)
{
- pdfFont = _document.FontTable.GetFont(idName, fontData);
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ pdfFont = Document.FontTable.GetFont(idName, fontData);
Debug.Assert(pdfFont != null);
string name = Resources.AddFont(pdfFont);
return name;
@@ -82,7 +83,8 @@ string IContentStream.GetFontName(string idName, byte[] fontData, out PdfFont pd
///
internal string GetImageName(XImage image)
{
- PdfImage pdfImage = _document.ImageTable.GetImage(image);
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ PdfImage pdfImage = Document.ImageTable.GetImage(image);
Debug.Assert(pdfImage != null);
string name = Resources.AddImage(pdfImage);
return name;
@@ -101,7 +103,7 @@ string IContentStream.GetImageName(XImage image)
///
internal string GetFormName(XForm form)
{
- PdfFormXObject pdfForm = _document.FormTable.GetForm(form);
+ PdfFormXObject pdfForm = Document.FormTable.GetForm(form);
Debug.Assert(pdfForm != null);
string name = Resources.AddForm(pdfForm);
return name;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfInternals.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDocumentInternals.cs
similarity index 82%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfInternals.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDocumentInternals.cs
index 376520ee..79e5ec84 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfInternals.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDocumentInternals.cs
@@ -11,17 +11,17 @@ namespace PdfSharp.Pdf.Advanced
/// Provides access to the internal document data structures.
/// This class prevents the public interfaces from pollution with too many internal functions.
///
- public class PdfInternals // TODO_OLD: PdfDocumentInternals... PdfPageInternals etc.
+ public class PdfDocumentInternals
{
- internal PdfInternals(PdfDocument document)
+ internal PdfDocumentInternals(PdfDocument document)
{
_document = document;
}
-
readonly PdfDocument _document;
///
/// Gets or sets the first document identifier.
+ /// Sometimes called the document ID.
///
public string FirstDocumentID
{
@@ -36,7 +36,8 @@ public string FirstDocumentID
///
/// Gets or sets the second document identifier.
- ///
+ /// Sometimes called the instance IC.
+ /// ///
public string SecondDocumentID
{
get => _document.Trailer.GetDocumentID(1);
@@ -76,7 +77,7 @@ Guid GuidFromString(string id)
// ReSharper disable once InconsistentNaming
// #PDF-UA
public object? UAManager
- => _document._uaManager;
+ => _document.UAManager;
///
/// Returns the object with the specified Identifier, or null if no such object exists.
@@ -155,31 +156,20 @@ public PdfObject[] GetAllObjects()
///
/// Gets all indirect objects ordered by their object identifier.
///
- [Obsolete("Use GetAllObjects.")] // Properties should not return arrays
+ [Obsolete("Use GetAllObjects.")] // Because a properties should not return an array.
public PdfObject[] AllObjects => GetAllObjects();
///
/// Creates the indirect object of the specified type, adds it to the document,
/// and returns the object.
///
- public T CreateIndirectObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>()
+ public T CreateIndirectObject<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ T>()
where T : PdfObject
{
-#if true
T obj = Activator.CreateInstance();
_document.IrefTable.Add(obj);
-#else
- T result = null;
- ConstructorInfo ctorInfo = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.ExactBinding,
- null, new Type[] { typeof(PdfDocument) }, null);
- if (ctorInfo != null)
- {
- result = (T)ctorInfo.Invoke(new object[] { _document });
- Debug.Assert(result != null);
- AddObject(result);
- }
- Debug.Assert(result != null, "CreateIndirectObject failed with type " + typeof(T).FullName);
-#endif
return obj;
}
@@ -189,12 +179,26 @@ public PdfObject[] GetAllObjects()
///
public void AddObject(PdfObject obj)
{
- if (obj == null)
- throw new ArgumentNullException(nameof(obj));
- if (obj.Owner == null!)
- obj.Document = _document;
- else if (obj.Owner != _document)
- throw new InvalidOperationException("Object does not belong to this document.");
+ // TODO: DELETE, moved to IrefTable
+ //if (obj == null)
+ // throw new ArgumentNullException(nameof(obj));
+
+ //// TODO
+ //if (obj.ParentInfo != null)
+ // throw new InvalidOperationException(
+ // "You cannot convert a PDF object to an indirect object when it was already used as a direct one.");
+
+ //if (obj.Owner == null!)
+ // obj.Document = _document;
+ //else if (obj.Owner != _document)
+ // throw new InvalidOperationException("PDF object does not belong to this document.");
+
+ // Make it an indirect object.
+ _document.IrefTable.Add(obj);
+ }
+
+ internal void TryAddObject(PdfObject obj) // TODO: remove
+ {
_document.IrefTable.Add(obj);
}
@@ -248,6 +252,16 @@ public void WriteObject(Stream stream, PdfItem item)
///
/// The name of the custom value key.
///
- public string CustomValueKey = "/PdfSharp.CustomValue";
+ public readonly string CustomValueKey = "/PdfSharp.CustomValue";
+ }
+
+ ///
+ /// The class PdfInternals was renamed to PdfDocumentInternals.
+ /// Just rename it.
+ ///
+ [Obsolete("PDFsharp 6.4: PdfInternals was renamed to PdfDocumentInternals.")]
+ public class PdfInternals
+ {
+ // Just a stub for a better compiler message.
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileParameters.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileParameters.cs
new file mode 100644
index 00000000..3d20b523
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileParameters.cs
@@ -0,0 +1,75 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using System.Security.Cryptography;
+using PdfSharp.Pdf.PdfItemExtensions;
+using PdfSharp.Pdf.Security;
+
+namespace PdfSharp.Pdf.Advanced
+{
+ ///
+ /// Represents an embedded file parameter dictionary.
+ ///
+ public sealed class PdfEmbeddedFileParameters : PdfDictionary
+ {
+ // Reference 2.0: 7.11.4 Embedded file streams / Page 138
+
+ ///
+ /// Initializes a new instance of PdfEmbeddedFileParameters.
+ ///
+ public PdfEmbeddedFileParameters(PdfDocument document) : base(document)
+ {
+ var now = new PdfDate(DateTimeOffset.Now);
+ Elements[Keys.CreationDate] = now;
+ Elements[Keys.ModDate] = now;
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfEmbeddedFileParameters(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : PdfStream.Keys
+ {
+ ///
+ /// (Optional) The size of the uncompressed embedded file, in bytes.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string Size = "/Size";
+
+ ///
+ /// (Optional) The date and time when the embedded file was created.
+ ///
+ [KeyInfo(KeyType.Date | KeyType.Optional)]
+ public const string CreationDate = "/CreationDate";
+
+ ///
+ /// (Optional, required in the case of an embedded file stream used as an associated file)
+ /// The date and time when the embedded file was last modified.
+ ///
+ [KeyInfo(KeyType.Date | KeyType.Optional)]
+ public const string ModDate = "/ModDate";
+
+ ///
+ /// (Optional; deprecated in PDF 2.0)
+ /// A subdictionary containing additional information specific to Mac OS files.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string Mac = "/Mac";
+
+ ///
+ ///(Optional) A 16-byte string that is the checksum of the bytes of the uncompressed embedded file.
+ /// The checksum shall be calculated by applying the standard MD5 message-digest algorithm
+ /// (defined in Internet RFC 1321) to the bytes of the embedded file stream.
+ ///
+ [KeyInfo(KeyType.String | KeyType.Optional)]
+ public const string CheckSum = "/CheckSum";
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs
index 9b403097..cc32c03c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs
@@ -1,40 +1,77 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Pdf.PdfItemExtensions;
+using PdfSharp.Pdf.Security;
+
namespace PdfSharp.Pdf.Advanced
{
///
- /// Represents an embedded file stream.
- /// PDF 1.3.
+ /// Represents a PDF embedded file stream.
///
public class PdfEmbeddedFileStream : PdfDictionary
{
+ // Reference 2.0: 7.11.4 Embedded file streams / Page 137
+
///
/// Initializes a new instance of PdfEmbeddedFileStream from a stream.
///
- public PdfEmbeddedFileStream(PdfDocument document, Stream stream) : base(document)
+ public PdfEmbeddedFileStream(PdfDocument document, Stream stream, string? subType = null, DateTimeOffset? modDate = null)
+ : base(document)
{
- _data = new byte[stream.Length];
+ // TODO: Use TryGetBuffer if stream is MemoryStream.
+ // use the byte array from stream function here.
+
+ var length = stream.Length;
+ var bytes = new byte[length];
using (stream)
{
- stream.Read(_data, 0, (int)stream.Length);
+ var bytesRead = stream.Read(bytes, 0, (int)length);
+ // TODO: bytesRead may be smaller than length.
+ //Debug.Assert(bytesRead != length);
}
- Initialize();
+ InitializeNew(bytes, subType, modDate);
}
- void Initialize()
+ ///
+ /// Initializes a new instance of PdfEmbeddedFileStream from a stream.
+ ///
+ public PdfEmbeddedFileStream(PdfDocument document, byte[] bytes, string? subType = null, DateTimeOffset? modDate = null)
+ : base(document)
+ {
+ InitializeNew(bytes, subType, modDate);
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfEmbeddedFileStream(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void InitializeNew(byte[] bytes, string? subType, DateTimeOffset? modDate)
{
Elements.SetName(Keys.Type, TypeValue);
- Elements.SetInteger("/Length", _data.Length);
+ Elements.SetInteger("/Length", bytes.Length);
+
+ // HACK TODO remove
+ Elements.SetName(Keys.Subtype, "text/xml");
+ if (subType != null)
+ Elements.SetName(Keys.Subtype, subType);
+
+ var now = DateTimeOffset.Now;
+ modDate ??= now;
- var objParams = new PdfDictionary(_document);
- objParams.Elements.SetInteger("/Size", _data.Length);
- var now = DateTime.Now;
- objParams.Elements.SetDateTime("/CreationDate", now);
- objParams.Elements.SetDateTime("/ModDate", now);
- Elements.SetObject(Keys.Params, objParams);
+ //var fileParams = Elements.GetValue(Keys.Params, VCF.Create).AsDictionary();
+ var fileParams = Elements.GetRequiredDictionary(Keys.Params, VCF.Create);
- Stream = new PdfStream(_data, this);
+ fileParams.Elements.SetInteger(PdfEmbeddedFileParameters.Keys.Size, bytes.Length);
+ fileParams.Elements.SetDateTime(PdfEmbeddedFileParameters.Keys.CreationDate, now);
+ fileParams.Elements.SetDateTime(PdfEmbeddedFileParameters.Keys.ModDate, modDate.Value);
+ fileParams.Elements.SetString(PdfEmbeddedFileParameters.Keys.CheckSum, MD5Managed.ComputeHashHex(bytes));
+
+ Stream = new(bytes, this);
}
///
@@ -45,8 +82,6 @@ public static bool IsEmbeddedFile(PdfDictionary dictionary)
return dictionary.Elements.GetName(Keys.Type) == TypeValue;
}
- readonly byte[] _data;
-
const string TypeValue = "/EmbeddedFile";
///
@@ -75,8 +110,17 @@ public class Keys : PdfStream.Keys
/// (Optional) An embedded file parameter dictionary containing additional,
/// file-specific information (see Table 3.43).
///
- [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(PdfEmbeddedFileParameters))]
public const string Params = "/Params";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
}
+
+ internal override DictionaryMeta Meta => Keys.Meta;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfExtGState.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfExtGState.cs
index 8c8eef34..8627e2cf 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfExtGState.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfExtGState.cs
@@ -25,7 +25,6 @@ public PdfExtGState(PdfDocument document)
: base(document)
{
Elements.SetName(Keys.Type, "/ExtGState");
-
#if true_
//AIS false
//BM /Normal
@@ -53,6 +52,14 @@ public PdfExtGState(PdfDocument document)
//#endif
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfExtGState(PdfDictionary dict)
+ : base(dict)
+ { }
+
///
/// Used in Edf.Xps.
///
@@ -118,7 +125,8 @@ public double StrokeAlpha
{
// #PDF-A
// ReSharper disable once CompareOfFloatsByEqualityOperator
- if (_document.IsPdfA && value != 1.0)
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ if (Document.IsPdfA && value != 1.0)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Stroke alpha value set to 1.");
value = 1.0;
@@ -139,8 +147,9 @@ public double NonStrokeAlpha
set
{
// #PDF-A
+ Debug.Assert(ReferenceEquals(_document2, Document));
// ReSharper disable once CompareOfFloatsByEqualityOperator
- if (_document.IsPdfA && value != 1.0)
+ if (Document.IsPdfA && value != 1.0)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Non-stroke alpha value set to 1.");
value = 1.0;
@@ -188,7 +197,7 @@ public bool NonStrokeOverprint
///
public PdfSoftMask SoftMask
{
- set => Elements.SetReference(Keys.SMask, value);
+ set => Elements.SetObject(Keys.SMask, value);
}
internal string Key => _key;
@@ -211,7 +220,7 @@ internal static string MakeKey(double alpha, bool overPaint)
///
/// Common keys for all streams.
///
- internal sealed class Keys : KeysBase
+ public sealed class Keys : KeysBase
{
// ReSharper disable InconsistentNaming
@@ -384,7 +393,6 @@ internal sealed class Keys : KeysBase
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFont.cs
index c3cceb6a..53853243 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFont.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFont.cs
@@ -1,8 +1,9 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal.OpenType;
using PdfSharp.Fonts;
-using PdfSharp.Fonts.Internal;
+using CodePointGlyphIndexPair = PdfSharp.Internal.OpenType.CodePointGlyphIndexPair;
namespace PdfSharp.Pdf.Advanced
{
@@ -16,7 +17,19 @@ public class PdfFont : PdfDictionary
///
protected PdfFont(PdfDocument document)
: base(document)
- { }
+ {
+ CMapInfo = null!;
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFont(PdfDictionary dict)
+ : base(dict)
+ {
+ CMapInfo = null!;
+ }
internal PdfFontDescriptor FontDescriptor
{
@@ -25,9 +38,9 @@ internal PdfFontDescriptor FontDescriptor
Debug.Assert(_fontDescriptor != null);
return _fontDescriptor ?? NRT.ThrowOnNull();
}
- set => _fontDescriptor = value;
+ init => _fontDescriptor = value;
}
- PdfFontDescriptor _fontDescriptor = default!;
+ readonly PdfFontDescriptor _fontDescriptor = null!;
internal PdfFontEncoding FontEncoding { get; init; }
@@ -38,20 +51,15 @@ internal PdfFontDescriptor FontDescriptor
internal void AddChars(CodePointGlyphIndexPair[] codePoints)
{
- _cmapInfo.AddChars(codePoints);
+ CMapInfo.AddChars(codePoints);
_fontDescriptor.CMapInfo.AddChars(codePoints);
}
///
/// Gets or sets the CMapInfo of a PDF font.
- /// For a Unicode font only this characters come to the ToUnicode map.
+ /// For a Unicode font only these characters come to the ToUnicode map.
///
- internal CMapInfo CMapInfo
- {
- get => _cmapInfo ?? NRT.ThrowOnNull();
- set => _cmapInfo = value;
- }
- internal CMapInfo _cmapInfo = default!;
+ internal CMapInfo CMapInfo { get; init; }
///
/// Gets or sets ToUnicodeMap.
@@ -61,6 +69,7 @@ internal PdfToUnicodeMap ToUnicodeMap
get => _toUnicodeMap ?? NRT.ThrowOnNull();
set => _toUnicodeMap = value;
}
+
// ReSharper disable once InconsistentNaming
internal PdfToUnicodeMap? _toUnicodeMap;
@@ -100,3 +109,23 @@ public class Keys : KeysBase
}
}
}
+
+namespace PdfSharp.Pdf.Advanced
+{
+ ///
+ /// An internal hack to allow to interact PdfSharp.Graphics.FontFace with
+ /// PDFsharp core lib.
+ ///
+ interface IFontProxy // #PSGFX IXGlyphTypeFaceProxy
+ {
+ void CheckVersion();
+
+ string Key { get; }
+
+ OpenTypeFontFace FontFace { get; }
+
+ string FaceName { get; }
+
+ string GetBaseName();
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptor.cs
index fe9bfdf1..1ef7f64d 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptor.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptor.cs
@@ -1,10 +1,9 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
using System.Text;
+using PdfSharp.Internal.OpenType;
using PdfSharp.Fonts;
-using PdfSharp.Fonts.OpenType;
namespace PdfSharp.Pdf.Advanced
{
@@ -75,11 +74,11 @@ enum PdfFontDescriptorFlags
///
public sealed class PdfFontDescriptor : PdfDictionary
{
- internal PdfFontDescriptor(PdfDocument document, OpenTypeDescriptor otDescriptor)
+ internal PdfFontDescriptor(PdfDocument document, OpenTypeFontDescriptor otDescriptor)
: base(document)
{
Descriptor = otDescriptor;
- _cmapInfo = new CMapInfo(otDescriptor);
+ _cmapInfo = new CMapInfo(otDescriptor.FontFace.GlyphCount);
Elements.SetName(Keys.Type, "/FontDescriptor");
@@ -97,9 +96,26 @@ internal PdfFontDescriptor(PdfDocument document, OpenTypeDescriptor otDescriptor
Elements.SetReal(Keys.ItalicAngle, Descriptor.ItalicAngle);
Elements.SetInteger(Keys.StemV, Descriptor.StemV);
Elements.SetInteger(Keys.XHeight, Descriptor.DesignUnitsToPdf(Descriptor.XHeight));
+
+ //var x = Elements.GetValue(Keys.FontBBox);
+ //if (x is { IsDead: true })
+ // Debugger.Break();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFontDescriptor(PdfDictionary dict)
+ : base(dict)
+ {
+ // Not implemented. Cannot create an OpenType descriptor from a font in a PDF file.
+ // Ctor exists only for type transformation.
+ Descriptor = null!; // TO/DO Connect with OT descriptor.
+ _cmapInfo = null!; // TO/DO Connect with OT descriptor.
}
- internal OpenTypeDescriptor Descriptor { get; }
+ internal OpenTypeFontDescriptor Descriptor { get; }
///
/// Gets or sets the name of the font.
@@ -107,7 +123,7 @@ internal PdfFontDescriptor(PdfDocument document, OpenTypeDescriptor otDescriptor
public string FontName
{
get => Elements.GetName(Keys.FontName);
- set => Elements.SetName(Keys.FontName, value);
+ set => Elements.SetName(Keys.FontName, Name.MakeName(value));
}
///
@@ -115,7 +131,7 @@ public string FontName
///
public bool IsSymbolFont { get; private set; }
- PdfFontDescriptorFlags FlagsFromDescriptor(OpenTypeDescriptor descriptor)
+ PdfFontDescriptorFlags FlagsFromDescriptor(OpenTypeFontDescriptor descriptor)
{
PdfFontDescriptorFlags flags = 0;
IsSymbolFont = descriptor.IsSymbolFont;
@@ -128,17 +144,13 @@ PdfFontDescriptorFlags FlagsFromDescriptor(OpenTypeDescriptor descriptor)
/// font subset.
///
internal bool AddCmapTable { get; set; }
-
+
///
/// Gets the CMapInfo for PDF font descriptor.
/// It contains all characters, ANSI and Unicode.
///
- internal CMapInfo CMapInfo
- {
- get => _cmapInfo ?? NRT.ThrowOnNull();
- //set => _cmapInfo2 = value;
- }
- CMapInfo _cmapInfo;
+ internal CMapInfo CMapInfo => _cmapInfo;
+ readonly CMapInfo _cmapInfo;
internal override void PrepareForSave()
{
@@ -146,7 +158,7 @@ internal override void PrepareForSave()
if (_prepared)
return;
_prepared = true;
-
+
var pdfFontFile = new PdfFontProgram(Owner);
pdfFontFile.CreateFontFileAndAddToDescriptor(this, _cmapInfo, !AddCmapTable);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptorCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptorCache.cs
index 269d5da3..422eb68c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptorCache.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptorCache.cs
@@ -1,11 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
-using PdfSharp.Drawing;
-using PdfSharp.Fonts;
-using PdfSharp.Fonts.OpenType;
-using PdfSharp.Internal;
+using System.Collections.Concurrent;
+using PdfSharp.Internal.OpenType;
namespace PdfSharp.Pdf.Advanced
{
@@ -21,13 +18,13 @@ sealed class PdfFontDescriptorCache(PdfDocument doc)
/// Gets the FontDescriptor identified by the specified XFont. If no such object
/// exists, a new FontDescriptor is created and added to the cache.
///
- public PdfFontDescriptor GetOrCreatePdfDescriptorFor(OpenTypeDescriptor otDescriptor, string baseName)
+ public PdfFontDescriptor GetOrCreatePdfDescriptorFor(OpenTypeFontDescriptor otDescriptor, string baseName)
{
if (!_cache.TryGetValue(otDescriptor.Key, out var pdfDescriptor))
{
pdfDescriptor = new PdfFontDescriptor(Owner, otDescriptor);
pdfDescriptor.FontName = pdfDescriptor.CreateEmbeddedFontSubsetName(baseName);
- _cache.Add(otDescriptor.Key, pdfDescriptor);
+ _cache.TryAdd(otDescriptor.Key, pdfDescriptor);
}
return pdfDescriptor;
}
@@ -37,6 +34,6 @@ public PdfFontDescriptor GetOrCreatePdfDescriptorFor(OpenTypeDescriptor otDescri
///
/// Maps OpenType descriptor to document specific PDF font descriptor.
///
- readonly Dictionary _cache = [];
+ readonly ConcurrentDictionary _cache = [];
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontProgram.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontProgram.cs
index 0c362698..3446a55e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontProgram.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontProgram.cs
@@ -1,9 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System.Text;
+using PdfSharp.Internal.OpenType;
using PdfSharp.Fonts;
-using PdfSharp.Fonts.OpenType;
using PdfSharp.Pdf.Filters;
namespace PdfSharp.Pdf.Advanced
@@ -20,23 +19,32 @@ internal PdfFontProgram(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFontProgram(PdfDictionary dict)
+ : base(dict)
+ { }
+
internal void CreateFontFileAndAddToDescriptor(PdfFontDescriptor pdfFontDescriptor, CMapInfo cmapInfo, bool cidFont)
{
- var x = pdfFontDescriptor.Elements[PdfFontDescriptor.Keys.FontFile2];
+ //var x = pdfFontDescriptor.Elements[PdfFontDescriptor.Keys.FontFile2];
+ var x = pdfFontDescriptor.Elements.GetValue(PdfFontDescriptor.Keys.FontFile2); // #US373
OpenTypeFontFace subSet = pdfFontDescriptor.Descriptor.FontFace.CreateFontSubset(cmapInfo.GlyphIndices, cidFont);
- byte[] fontData = subSet.FontSource.Bytes;
+ byte[] fontData = subSet.OTFontSource.Bytes;
Owner.Internals.AddObject(this);
- pdfFontDescriptor.Elements[PdfFontDescriptor.Keys.FontFile2] = Reference;
+ pdfFontDescriptor.Elements[PdfFontDescriptor.Keys.FontFile2] = RequiredReference;
Elements["/Length1"] = new PdfInteger(fontData.Length);
if (!Owner.Options.NoCompression)
{
- fontData = Filtering.FlateDecode.Encode(fontData, _document.Options.FlateEncodeMode);
- Elements["/Filter"] = new PdfName("/FlateDecode");
+ fontData = Filtering.FlateDecode.Encode(fontData, Document.Options.FlateEncodeMode);
+ Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
}
- Elements["/Length"] = new PdfInteger(fontData.Length);
+ Elements.SetInteger(PdfStream.Keys.Length, fontData.Length);
CreateStream(fontData);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontTable.cs
index 791d35dc..e2003cd4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontTable.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontTable.cs
@@ -11,11 +11,13 @@ enum FontType
///
/// TrueType with WinAnsi encoding.
+ /// PDF subtype is TrueType.
///
TrueTypeWinAnsi = 1, // #RENAME better name
///
/// TrueType with Identity-H or Identity-V encoding (Unicode).
+ /// PDF subtype is CIDFontType2.
///
Type0Unicode = 2, // #RENAME better name
}
@@ -33,7 +35,7 @@ public PdfFontTable(PdfDocument document)
{ }
///
- /// Gets a PdfFont from an XFont. If no PdfFont already exists, a new one is created.
+ /// Gets a PdfFont from an XGlyphTypeface. If no PdfFont already exists, a new one is created.
///
public PdfFont GetOrCreateFont(XGlyphTypeface glyphTypeface, FontType fontType)
{
@@ -106,7 +108,7 @@ internal static string ComputePdfFontKey(XGlyphTypeface glyphTypeface, FontType
// #NFM Use gtk here. But the gtk without simulation flags.
- var faceName = glyphTypeface.FontFace.FullFaceName.ToLowerInvariant();
+ var faceName = glyphTypeface.OTFontFace.FullFaceName.ToLowerInvariant();
var bold = glyphTypeface.IsBold;
var italic = glyphTypeface.IsItalic;
var type = fontType == FontType.TrueTypeWinAnsi ? "+A" : "+U";
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObject.cs
index 98f89b6c..bb68002a 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObject.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObject.cs
@@ -1,6 +1,9 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal;
+using PdfSharp.Drawing;
+using PdfSharp.Pdf.IO;
#if GDI
using System.Drawing;
using System.Drawing.Imaging;
@@ -8,7 +11,10 @@
#if WPF
using System.Windows.Media;
#endif
-using PdfSharp.Drawing;
+
+// v7.0.0 REVIEW
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Advanced
{
@@ -20,21 +26,31 @@ public sealed class PdfFormXObject : PdfXObject, IContentStream
internal PdfFormXObject(PdfDocument thisDocument)
: base(thisDocument)
{
- Elements.SetName(Keys.Type, "/XObject");
- Elements.SetName(Keys.Subtype, "/Form");
+ SetTypeKeys();
+ _form = null;
}
internal PdfFormXObject(PdfDocument thisDocument, XForm form)
: base(thisDocument)
{
// BUG_OLD: form is not used - not implemented.
- Elements.SetName(Keys.Type, "/XObject");
- Elements.SetName(Keys.Subtype, "/Form");
+ SetTypeKeys();
+ _form = form;
//if (form.IsTemplate)
//{ }
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormXObject(PdfDictionary dict)
+ : base(dict)
+ {
+ _form = null;
+ }
+
internal double DpiX { get; set; } = 72;
internal double DpiY { get; set; } = 72;
@@ -44,8 +60,7 @@ internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importe
{
Debug.Assert(importedObjectTable != null);
Debug.Assert(ReferenceEquals(thisDocument, importedObjectTable.Owner));
- Elements.SetName(Keys.Type, "/XObject");
- Elements.SetName(Keys.Subtype, "/Form");
+ SetTypeKeys();
if (form.IsTemplate)
{
@@ -62,16 +77,19 @@ internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importe
PdfPage importPage = importPages[pdfForm.PageNumber - 1];
// Import resources
- var res = importPage.Elements["/Resources"];
- if (res != null) // unlikely but possible
+ //var res = importPage.Elements["/Resources"];
+ var res = importPage.Elements.GetValue("/Resources"); // #US373
+ if (res != null) // Unlikely but possible.
{
#if true
// Get root object.
+ // #US373 begin
PdfObject root;
- if (res is PdfReference reference)
- root = reference.Value;
- else
+ //if (res is PdfReference reference)
+ // root = reference.Value;
+ //else
root = (PdfDictionary)res;
+ // #US373 end
root = ImportClosure(importedObjectTable, thisDocument, root);
// If the root was a direct object, make it indirect.
@@ -160,7 +178,7 @@ internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importe
}
// Take /Rotate into account.
- PdfRectangle rect = importPage.Elements.GetRectangle(PdfPage.InheritablePageKeys.MediaBox);
+ PdfRectangle rect = importPage.Elements.GetRequiredRectangle(PdfPage.InheritablePageKeys.MediaBox);
// Reduce rotation to 0, 90, 180, or 270.
int rotate = (importPage.Elements.GetInteger(PdfPage.InheritablePageKeys.Rotate) % 360 + 360) % 360;
//rotate = 0;
@@ -209,13 +227,25 @@ internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importe
#if !DEBUG
content.Compressed = true;
#endif
- var filter = content.Elements["/Filter"];
+ //var filter = content.Elements["/Filter"];
+ var filter = content.Elements.GetValue(PdfStream.Keys.Filter); // #US373
if (filter != null)
- Elements["/Filter"] = filter.Clone();
+ Elements[PdfStream.Keys.Filter] = filter.Clone(); // #US373: Should we expect references here?
// (no cloning needed because the bytes keep untouched)
Stream = content.Stream; // new PdfStream(bytes, this);
- Elements.SetInteger("/Length", content.Stream.Value.Length);
+ Elements.SetInteger("/Length", content.Stream!.Value.Length);
+ }
+
+ public void SetTypeKeys()
+ {
+ Elements.SetName(Keys.Type, "/XObject");
+ Elements.SetName(Keys.Subtype, "/Form");
+ }
+
+ internal void SetForm(XForm form)
+ {
+ _form = form;
}
///
@@ -236,13 +266,14 @@ public PdfResources Resources
internal string GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont)
{
- pdfFont = _document.FontTable.GetOrCreateFont(glyphTypeface, fontType);
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ pdfFont = Document.FontTable.GetOrCreateFont(glyphTypeface, fontType);
Debug.Assert(pdfFont != null);
string name = Resources.AddFont(pdfFont);
return name;
}
- string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont)
+ string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont)
=> GetFontName(glyphTypeface, fontType, out pdfFont);
///
@@ -250,7 +281,8 @@ string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontTyp
///
internal string GetFontName(string idName, byte[] fontData, out PdfFont pdfFont)
{
- pdfFont = _document.FontTable.GetFont(idName, fontData);
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ pdfFont = Document.FontTable.GetFont(idName, fontData);
Debug.Assert(pdfFont != null);
string name = Resources.AddFont(pdfFont);
return name;
@@ -363,6 +395,26 @@ void FixUpObject_old(PdfImportedObjectTable iot, PdfObject value)
}
}
#endif
+ public static bool IsFormXObject(PdfDictionary dict)
+ {
+ // Subtype is required.
+ if (dict.Elements.GetName(Keys.Subtype) == "/Form")
+ return true;
+
+ // Type is optional.
+ if (dict.Elements.GetName(Keys.Type) == "/XObject")
+ return true;
+
+ return false;
+ }
+
+ internal override void WriteObject(PdfWriter writer)
+ {
+ _form?.DrawingFinished();
+ base.WriteObject(writer);
+ }
+
+ XForm? _form;
///
/// Predefined keys of this dictionary.
@@ -441,7 +493,6 @@ void FixUpObject_old(PdfImportedObjectTable iot, PdfObject value)
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObjectTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObjectTable.cs
index 188c9aa9..c85dbae0 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObjectTable.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObjectTable.cs
@@ -69,15 +69,15 @@ public PdfFormXObject GetForm(XForm form)
}
///
- /// Gets the imported object table.
+ /// Gets the imported object table of the specified page.
///
public PdfImportedObjectTable GetImportedObjectTable(PdfPage page)
{
// Is the external PDF file from which is imported already known for the current document?
- Selector selector = new Selector(page);
+ var selector = new Selector(page);
if (!_forms.TryGetValue(selector, out var importedObjectTable))
{
- importedObjectTable = new PdfImportedObjectTable(Owner, page.Owner);
+ importedObjectTable = new(Owner, page.Owner);
_forms[selector] = importedObjectTable;
}
return importedObjectTable;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfGroupAttributes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfGroupAttributes.cs
index 97cea4d1..9707a50b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfGroupAttributes.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfGroupAttributes.cs
@@ -22,6 +22,16 @@ internal PdfGroupAttributes(PdfDocument thisDocument)
Elements.SetName(Keys.Type, "/Group");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfGroupAttributes(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.Type, "/Group");
+ }
+
///
/// Predefined keys of this dictionary.
///
@@ -47,7 +57,6 @@ public class Keys : KeysBase
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.FaxEncode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.FaxEncode.cs
index e923780e..b7a51adc 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.FaxEncode.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.FaxEncode.cs
@@ -252,42 +252,42 @@ partial class PdfImage
static readonly uint[] _zeroRuns =
[
- 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */
- 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 - 0xff */
- ];
-
- static readonly uint[] _oneRuns =
- [
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6f */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 - 0x8f */
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 - 0x9f */
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xa0 - 0xaf */
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xb0 - 0xbf */
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 - 0xcf */
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xd0 - 0xdf */
- 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 - 0xef */
- 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8 /* 0xf0 - 0xff */
+ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, // 0x00 - 0x0f
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0x10 - 0x1f
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x20 - 0x2f
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x30 - 0x3f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 - 0x8f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xc0 - 0xcf
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xd0 - 0xdf
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xe0 - 0xef
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xf0 - 0xff
+ ];
+
+ static readonly uint[] _oneRuns =
+ [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20 - 0x2f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30 - 0x3f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 - 0x4f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x50 - 0x5f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x6f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x7f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 - 0x8f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 - 0x9f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 - 0xaf
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 - 0xbf
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 - 0xcf
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 - 0xdf
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 - 0xef
+ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8 // 0xf0 - 0xff
];
///
@@ -298,7 +298,7 @@ partial class PdfImage
static uint CountOneBits(BitReader reader, uint bitsLeft)
{
uint found = 0;
- for (;;)
+ for (; ; )
{
uint bits;
int @byte = reader.PeekByte(out bits);
@@ -325,7 +325,7 @@ static uint CountOneBits(BitReader reader, uint bitsLeft)
static uint CountZeroBits(BitReader reader, uint bitsLeft)
{
uint found = 0;
- for (;;)
+ for (; ; )
{
uint bits;
int @byte = reader.PeekByte(out bits);
@@ -418,7 +418,7 @@ static void FaxEncode2DRow(BitWriter writer, uint bytesFileOffset, byte[] imageB
uint a2, b2;
// ReSharper restore TooWideLocalVariableScope
- for (;;)
+ for (; ; )
{
b2 = FindDifferenceWithCheck(readerReference, b1, width, readerReference.GetBit(b1));
if (b2 >= a1)
@@ -699,13 +699,13 @@ internal void FlushBuffer()
internal void WriteBits(uint value, uint bits)
{
#if true
- // TODO_OLD: Try to make this faster!
+ // TODO_OLD: Try to make this faster!
- // If we have to write more bits than fit into the buffer, we fill
- // the buffer and call the same routine recursively for the rest.
+ // If we have to write more bits than fit into the buffer, we fill
+ // the buffer and call the same routine recursively for the rest.
#if USE_GOTO
- // Use GOTO instead of end recursion: (is this faster?)
- SimulateRecursion:
+ // Use GOTO instead of end recursion: (is this faster?)
+ SimulateRecursion:
#endif
if (bits + _bitsInBuffer > 8)
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs
index a12381ac..1c9afe14 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs
@@ -10,7 +10,7 @@
#endif
using Microsoft.Extensions.Logging;
using PdfSharp.Drawing;
-using PdfSharp.Drawing.Internal;
+using PdfSharp.Internal.Imaging;
using PdfSharp.Logging;
using PdfSharp.Pdf.Filters;
@@ -31,6 +31,7 @@ public PdfImage(PdfDocument document, XImage image)
Elements.SetName(Keys.Subtype, "/Image");
_image = image;
+ ImportedImage = _image._importedImage;
////// TODO_OLD: identify images used multiple times. If the image already exists use the same XRef.
////_defaultName = PdfImageTable.NextImageName;
@@ -39,7 +40,7 @@ public PdfImage(PdfDocument document, XImage image)
{
// Pdf supports Jpeg, therefore we can write what we’ve read:
case "{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Jpeg
- InitializeJpeg(document.Options);
+ InitializeJpeg();
break;
// All other image formats are converted to PDF bitmaps:
@@ -48,7 +49,7 @@ public PdfImage(PdfDocument document, XImage image)
case "{B96B3CB1-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Tiff
case "{B96B3CB5-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Icon
// Future improvement: try Jpeg for size optimization???
- InitializeNonJpeg(document.Options);
+ InitializeNonJpeg();
break;
case "{84570158-DBF0-4C6B-8368-62D6A3CA76E0}": //XImageFormat.Pdf:
@@ -61,148 +62,39 @@ public PdfImage(PdfDocument document, XImage image)
}
}
-#if true_
- //private void InitializeAg()
- //{
- // ReadTrueColorMemoryBitmapAg(3, 8, true);
- //}
-
- //private void ReadTrueColorMemoryBitmapAg(int components, int bits, bool hasAlpha)
- //{
- // int pdfVersion = Owner.Version;
- // MemoryStream memory = new MemoryStream();
-
- // WriteableBitmap bitmap = null;
- // if (_image._wpfImage is WriteableBitmap)
- // bitmap = (WriteableBitmap)_image._wpfImage;
- // else if (_image._wpfImage is BitmapImage)
- // bitmap = new WriteableBitmap(_image._wpfImage);
-
- // if (bitmap != null)
- // {
- // int height = _image.PixelHeight;
- // int width = _image.PixelWidth;
-
- // int logicalComponents = components;
- // if (components == 4)
- // logicalComponents = 3;
-
- // byte[] imageData = new byte[components * width * height];
-
- // bool hasMask = false;
- // bool hasAlphaMask = false;
- // byte[] alphaMask = hasAlpha ? new byte[width * height] : null;
- // MonochromeMask mask = hasAlpha ? new MonochromeMask(width, height) : null;
-
- // int nOffsetRead = 0;
- // if (logicalComponents == 3)
- // {
- // for (int y = 0; y < height; ++y)
- // {
- // int nOffsetWrite = 3 * y * width; // 3*(height - 1 - y)*width;
- // int nOffsetWriteAlpha = 0;
- // if (hasAlpha)
- // {
- // mask.StartLine(y);
- // nOffsetWriteAlpha = y * width; // (height - 1 - y) * width;
- // }
-
- // for (int x = 0; x < width; ++x)
- // {
- // uint pixel = (uint)bitmap.Pixels[nOffsetRead];
- // imageData[nOffsetWrite] = (byte)(pixel >> 16);
- // imageData[nOffsetWrite + 1] = (byte)(pixel >> 8);
- // imageData[nOffsetWrite + 2] = (byte)(pixel);
- // if (hasAlpha)
- // {
- // byte pel = (byte)(pixel >> 24);
- // mask.AddPel(pel);
- // alphaMask[nOffsetWriteAlpha] = pel;
- // if (!hasMask || !hasAlphaMask)
- // {
- // if (pel != 255)
- // {
- // hasMask = true;
- // if (pel != 0)
- // hasAlphaMask = true;
- // }
- // }
- // ++nOffsetWriteAlpha;
- // }
- // //nOffsetRead += hasAlpha ? 4 : components;
- // ++nOffsetRead;
- // nOffsetWrite += 3;
- // }
- // //nOffsetRead = 4*((nOffsetRead + 3)/4); // Align to 32 bit boundary
- // }
- // }
- // else if (components == 1)
- // {
- // // Grayscale
- // throw new NotImplementedException("Image format not supported (grayscales).");
- // }
-
- // FlateDecode fd = new FlateDecode();
- // if (hasMask)
- // {
- // // monochrome mask is either sufficient or
- // // provided for compatibility with older reader versions
- // byte[] maskDataCompressed = fd.Encode(mask.MaskData, _document.Options.FlateEncodeMode);
- // PdfDictionary pdfMask = new PdfDictionary(_document);
- // pdfMask.Elements.SetName(Keys.Type, "/XObject");
- // pdfMask.Elements.SetName(Keys.Subtype, "/Image");
-
- // Owner._irefTable.Add(pdfMask);
- // pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
- // pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length);
- // pdfMask.Elements["/Filter"] = new PdfName("/FlateDecode");
- // pdfMask.Elements[Keys.Width] = new PdfInteger(width);
- // pdfMask.Elements[Keys.Height] = new PdfInteger(height);
- // pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
- // pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true);
- // Elements[Keys.Mask] = pdfMask.Reference;
- // }
- // if (hasMask && hasAlphaMask && pdfVersion >= 14)
- // {
- // // The image provides an alpha mask (requires Arcrobat 5.0 or higher)
- // byte[] alphaMaskCompressed = fd.Encode(alphaMask, _document.Options.FlateEncodeMode);
- // PdfDictionary smask = new PdfDictionary(_document);
- // smask.Elements.SetName(Keys.Type, "/XObject");
- // smask.Elements.SetName(Keys.Subtype, "/Image");
-
- // Owner._irefTable.Add(smask);
- // smask.Stream = new PdfStream(alphaMaskCompressed, smask);
- // smask.Elements["/Length"] = new PdfInteger(alphaMaskCompressed.Length);
- // smask.Elements["/Filter"] = new PdfName("/FlateDecode");
- // smask.Elements[Keys.Width] = new PdfInteger(width);
- // smask.Elements[Keys.Height] = new PdfInteger(height);
- // smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8);
- // smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
- // Elements[Keys.SMask] = smask.Reference;
- // }
-
- // byte[] imageDataCompressed = fd.Encode(imageData, _document.Options.FlateEncodeMode);
-
- // Stream = new PdfStream(imageDataCompressed, this);
- // Elements["/Length"] = new PdfInteger(imageDataCompressed.Length);
- // Elements["/Filter"] = new PdfName("/FlateDecode");
- // Elements[Keys.Width] = new PdfInteger(width);
- // Elements[Keys.Height] = new PdfInteger(height);
- // Elements[Keys.BitsPerComponent] = new PdfInteger(8);
- // // Anything needed for CMYK? Do we have sample images?
- // Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB");
- // if (_image.Interpolate)
- // Elements[Keys.Interpolate] = PdfBoolean.True;
- // }
- //}
+#if CORE
+ internal PdfImage(PdfDocument document, ImportedImage importedImage)
+ : base(document)
+ {
+ Elements.SetName(Keys.Type, "/XObject");
+ Elements.SetName(Keys.Subtype, "/Image");
+ _image = null!; // Image is not used in this case.
+ ImportedImage = importedImage;
+ if (ImportedImage is ImportedJpegImage)
+ InitializeJpeg();
+ else
+ InitializeNonJpeg();
+ }
#endif
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfImage(PdfDictionary dict)
+ : base(dict)
+ {
+ _image = null!;
+ }
+
///
/// Gets the underlying XImage object.
///
- public XImage Image => _image;
+ public XImage? Image => _image;
- readonly XImage _image;
+ readonly XImage? _image;
+
+ ImportedImage? ImportedImage { get; set; }
///
/// Returns 'Image'.
@@ -215,7 +107,7 @@ public override string ToString()
///
/// Creates the keys for a JPEG image.
///
- void InitializeJpeg(PdfDocumentOptions options)
+ void InitializeJpeg()
{
// PDF supports JPEG, so there’s not much to be done.
MemoryStream? memory = null;
@@ -226,16 +118,16 @@ void InitializeJpeg(PdfDocumentOptions options)
int streamLength = 0;
#if CORE || GDI || WPF
- if (_image._importedImage != null)
+ if (ImportedImage != null)
{
- var idd = (ImageDataDct)_image._importedImage.ImageData(options);
- imageBits = idd.Data;
+ var idd = ImportedImage.ImageData;
+ imageBits = idd;
streamLength = idd.Length;
}
#endif
#if CORE || GDI
- if (_image._importedImage == null)
+ if (_image != null && ImportedImage == null)
{
if (!_image._path.StartsWith("*", StringComparison.Ordinal))
{
@@ -258,7 +150,7 @@ void InitializeJpeg(PdfDocumentOptions options)
else
{
// If we have a stream, copy data from the stream.
- if (_image._stream != null && _image._stream.CanSeek)
+ if (_image._stream != null! && _image._stream.CanSeek)
{
memory = new MemoryStream();
ownMemory = true;
@@ -273,51 +165,54 @@ void InitializeJpeg(PdfDocumentOptions options)
}
else
{
- // TODO_OLD Anything we can do here?
- //#if CORE _WITH _GDI
- // // No stream, no filename, get image data.
- // // Save the image to a memory stream.
- // _image._gdiImage.Save(memory, ImageFormat.Jpeg);
- //#endif
+#if GDI
+ // No stream, no filename, get image data from GDI image.
+ // Save the image to a memory stream.
+ memory = new MemoryStream();
+ ownMemory = true;
+ _image._gdiImage.Save(memory, System.Drawing.Imaging.ImageFormat.Jpeg);
+#endif
}
}
if (memory is null || (int)memory.Length == 0)
{
Debug.Assert(false, "Internal error? JPEG image, but file not found!");
+ throw new InvalidOperationException("JPEG image used, but cannot access image data!");
}
-
#if GDI
{
// Use ImageImporter to get meta information about JPEG image. GDI does not return correct information for CMYK images.
- var ii = ImageImporter.GetImageImporter();
- var i = ii.ImportImage(memory);
- if (i != null && i is ImportedImageJpeg)
+ var ii = ToBeNamed.TryImportImage(memory, out var image);
+ if (ii && image is ImportedJpegImage)
{
- _image._importedImage = i;
+ _image._importedImage = image;
+ ImportedImage = image;
}
}
#endif
}
#endif
#if WPF
- if (!_image._path.StartsWith("*", StringComparison.Ordinal))
+ if (_image != null && !_image._path.StartsWith("*", StringComparison.Ordinal))
{
// Image does not come from a stream, so we have the path to the file - just use the path.
// If the image was modified in memory, those changes will be lost and the original image, as it was read from the file, will be added to the PDF.
if (imageBits == null)
{
- // Use ImageImporterJpeg to read the image.
+ // Use ImageImporter to read the image.
string filename = XImage.GetImageFilename(_image._wpfImage);
- ImageImporter ii = ImageImporter.GetImageImporter();
- var i = ii.ImportImage(filename);
+ var worker = new StreamReaderWorker(filename);
+ var success = ToBeNamed.TryImportImage(worker.Data, out var image);
- ImageDataDct idd2 = (ImageDataDct)i!.ImageData(options);
- imageBits = idd2.Data;
- streamLength = idd2.Length;
+ imageBits = image!.GetPdfImageData();
+ streamLength = imageBits.Length;
if (_image._importedImage == null)
- _image._importedImage = i;
+ {
+ _image._importedImage = image;
+ ImportedImage = image;
+ }
}
memory = _image.Memory;
@@ -325,7 +220,7 @@ void InitializeJpeg(PdfDocumentOptions options)
else
{
// If we have a stream, copy data from the stream.
- if (_image._stream != null && _image._stream.CanSeek)
+ if (_image != null && _image._stream != null! && _image._stream.CanSeek)
{
memory = new MemoryStream();
ownMemory = true;
@@ -338,18 +233,9 @@ void InitializeJpeg(PdfDocumentOptions options)
memory.Write(buffer, 0, bytesRead);
}
}
- else
- {
- // TODO_OLD Anything we can do here?
- //#if CORE _WITH _GDI
- // // No stream, no filename, get image data.
- // // Save the image to a memory stream.
- // _image._gdiImage.Save(memory, ImageFormat.Jpeg);
- //#endif
- }
}
- if (imageBits == null && memory == null && _image._wpfImage != null)
+ if (imageBits == null && memory == null && _image != null && _image._wpfImage != null)
{
Debug.Assert(false, "Internal error? JPEG image, but file not read!");
}
@@ -358,58 +244,57 @@ void InitializeJpeg(PdfDocumentOptions options)
memory = new MemoryStream();
ownMemory = true;
#endif
- // Idea: Use ImageImporterJPEG here to avoid redundant code.
-
if (imageBits == null)
{
streamLength = (int)memory!.Length; // NRT
imageBits = new byte[streamLength];
memory.Seek(0, SeekOrigin.Begin);
- memory.Read(imageBits, 0, streamLength);
+ _ = memory.Read(imageBits, 0, streamLength);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (ownMemory)
memory.Dispose();
}
-
- bool tryFlateDecode = _document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Automatic;
- bool useFlateDecode = _document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Always;
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ bool tryFlateDecode = Document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Automatic;
+ bool useFlateDecode = Document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Always;
FlateDecode fd = new FlateDecode();
- byte[]? imageDataCompressed = useFlateDecode || tryFlateDecode ? fd.Encode(imageBits, _document.Options.FlateEncodeMode) : null;
- if (imageDataCompressed != null && (useFlateDecode || tryFlateDecode && imageDataCompressed.Length < imageBits.Length))
+ byte[]? imageDataCompressed = useFlateDecode || tryFlateDecode ? fd.Encode(imageBits, Document.Options.FlateEncodeMode) : null;
+ if (imageDataCompressed != null && (useFlateDecode || (tryFlateDecode && imageDataCompressed.Length < imageBits.Length)))
{
Stream = new PdfStream(imageDataCompressed, this);
- Elements["/Length"] = new PdfInteger(imageDataCompressed.Length);
- PdfArray arrayFilters = new(_document);
+ Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length);
+ PdfArray arrayFilters = new(Document);
arrayFilters.Elements.Add(new PdfName("/FlateDecode"));
arrayFilters.Elements.Add(new PdfName("/DCTDecode"));
- Elements["/Filter"] = arrayFilters;
+ Elements[PdfStream.Keys.Filter] = arrayFilters;
}
else
{
Stream = new PdfStream(imageBits, this);
- Elements["/Length"] = new PdfInteger(streamLength);
- Elements["/Filter"] = new PdfName("/DCTDecode");
+ Elements.SetInteger(PdfStream.Keys.Length, streamLength);
+ Elements.SetName(PdfStream.Keys.Filter, "/DCTDecode");
}
+
// #PDF-A
- if (_image.Interpolate && !_document.IsPdfA)
+ if (_image is { Interpolate: true } && !Document.IsPdfA)
Elements[Keys.Interpolate] = PdfBoolean.True;
- Elements[Keys.Width] = new PdfInteger(_image.PixelWidth);
- Elements[Keys.Height] = new PdfInteger(_image.PixelHeight);
+ Elements[Keys.Width] = new PdfInteger(_image?.PixelWidth ?? ImportedImage!.PixelWidth);
+ Elements[Keys.Height] = new PdfInteger(_image?.PixelHeight ?? ImportedImage!.PixelHeight);
Elements[Keys.BitsPerComponent] = new PdfInteger(8);
#if CORE || GDI || WPF
- if (_image._importedImage != null)
+ if (ImportedImage != null)
{
- if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGCMYK ||
- _image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGRGBW)
+ if (ImportedImage.ImageFormat == PdfSharp.Internal.Imaging.ImageFormats.JPEGCMYK ||
+ ImportedImage.ImageFormat == PdfSharp.Internal.Imaging.ImageFormats.JPEGRGBW)
{
// TODO_OLD: Test with CMYK JPEG files (so far I only found ImageFlags.ColorSpaceYcck JPEG files ...)
Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK");
- if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGRGBW)
+ if (ImportedImage.ImageFormat == PdfSharp.Internal.Imaging.ImageFormats.JPEGRGBW)
Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors because YCCK is inverted CMYK.
}
- else if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGGRAY)
+ else if (ImportedImage.ImageFormat == PdfSharp.Internal.Imaging.ImageFormats.JPEGGRAY)
{
Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
}
@@ -420,14 +305,15 @@ void InitializeJpeg(PdfDocumentOptions options)
}
#endif
#if GDI
- if (_image._importedImage == null)
+ if (_image != null && ImportedImage == null)
{
if ((_image._gdiImage.Flags & ((int)ImageFlags.ColorSpaceCmyk | (int)ImageFlags.ColorSpaceYcck)) != 0)
{
// TODO_OLD: Test with CMYK JPEG files (so far I only found ImageFlags.ColorSpaceYcck JPEG files ...)
Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK");
if ((_image._gdiImage.Flags & (int)ImageFlags.ColorSpaceYcck) != 0)
- Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors because YCCK is inverted CMYK.
+ //Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors because YCCK is inverted CMYK. #US309
+ Elements["/Decode"] = new PdfArray(1, 0, 1, 0, 1, 0, 1, 0); // Invert colors because YCCK is inverted CMYK.
}
else if ((_image._gdiImage.Flags & (int)ImageFlags.ColorSpaceGray) != 0)
{
@@ -440,7 +326,7 @@ void InitializeJpeg(PdfDocumentOptions options)
}
#endif
#if WPF
- if (_image._importedImage == null)
+ if (_image != null && ImportedImage == null)
{
string pixelFormat = _image._wpfImage?.Format.ToString() ?? "";
bool isCmyk = _image.IsCmyk;
@@ -466,45 +352,46 @@ void InitializeJpeg(PdfDocumentOptions options)
///
/// Creates the keys for a FLATE image.
///
- void InitializeNonJpeg(PdfDocumentOptions options)
+ void InitializeNonJpeg()
{
#if CORE || GDI || WPF
- if (_image._importedImage != null)
+ if (ImportedImage != null)
{
- switch (_image._importedImage.Information.ImageFormat)
+ switch (ImportedImage.ImageFormat)
{
- case ImageInformation.ImageFormats.ARGB32:
- CreateTrueColorMemoryBitmap(3, 8, true, options);
+ case PdfSharp.Internal.Imaging.ImageFormats.Bgra32:
+ CreateTrueColorMemoryBitmap(3, 8, true);
break;
- case ImageInformation.ImageFormats.RGB24:
- CreateTrueColorMemoryBitmap(3, 8, false, options);
+ case PdfSharp.Internal.Imaging.ImageFormats.Bgr32:
+ CreateTrueColorMemoryBitmap(3, 8, false);
break;
- case ImageInformation.ImageFormats.Grayscale8:
- CreateTrueColorMemoryBitmap(1, 8, false, options);
+ case PdfSharp.Internal.Imaging.ImageFormats.Gray8:
+ CreateTrueColorMemoryBitmap(1, 8, false);
break;
- case ImageInformation.ImageFormats.Palette8:
- CreateIndexedMemoryBitmap(8, options);
+ case PdfSharp.Internal.Imaging.ImageFormats.Indexed8:
+ CreateIndexedMemoryBitmap(8);
break;
- case ImageInformation.ImageFormats.Palette4:
- CreateIndexedMemoryBitmap(4, options);
+ case PdfSharp.Internal.Imaging.ImageFormats.Indexed4:
+ CreateIndexedMemoryBitmap(4);
break;
- case ImageInformation.ImageFormats.Palette1:
- CreateIndexedMemoryBitmap(1, options);
+ case PdfSharp.Internal.Imaging.ImageFormats.Indexed1:
+ CreateIndexedMemoryBitmap(1);
break;
default:
- throw new NotImplementedException("Image format not supported.");
+ throw new NotSupportedException("Image format not supported.");
}
+ return;
}
#endif
#if GDI && !WPF
- switch (_image._gdiImage.PixelFormat)
+ switch (_image!._gdiImage.PixelFormat)
{
case PixelFormat.Format24bppRgb:
ReadTrueColorMemoryBitmap(3, 8, false);
@@ -532,14 +419,17 @@ void InitializeNonJpeg(PdfDocumentOptions options)
break;
default:
-//#if DEBUGxxx
-// image.image.Save("$$$.bmp", ImageFormat.Bmp);
-//#endif
- throw new NotImplementedException("Image format not supported.");
+ //#if DEBUGxxx
+ // image.image.Save("$$$.bmp", ImageFormat.Bmp);
+ //#endif
+ throw new NotSupportedException("Image format not supported.");
}
#endif
#if WPF
- string format = _image._wpfImage.Format.ToString();
+ // Only called for GDI and WPF.
+ // _image is not null here.
+
+ string format = _image!._wpfImage.Format.ToString();
switch (format)
{
case "Bgr24": //Format24bppRgb:
@@ -586,31 +476,41 @@ void InitializeNonJpeg(PdfDocumentOptions options)
#if DEBUG_
image.image.Save("$$$.bmp", ImageFormat.Bmp);
#endif
- throw new NotImplementedException("Image format \"" + format + "\" not supported.");
+ throw new NotSupportedException("Image format '" + format + "' not supported.");
}
#endif
}
#if CORE || GDI || WPF
- void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options)
+ void CreateIndexedMemoryBitmap(int bits)
{
- var idb = (ImageDataBitmap)_image._importedImage!.ImageData(options);
- var ii = _image._importedImage.Information;
+ var ii = (ImportedRasterImage)ImportedImage!;
+ var ibi = ii as ImportedBmpImage;
int pdfVersion = Owner.Version;
- int firstMaskColor = -1, lastMaskColor = -1;
+ int firstMaskColor = ibi?.FirstMaskColor ?? -1, lastMaskColor = ibi?.LastMaskColor ?? -1;
// TODO_OLD MaskColor transparency.
- bool segmentedColorMask = idb.SegmentedColorMask;
- bool hasAlphaMask = idb.AlphaMaskLength > 0;
+ bool segmentedColorMask = ibi?.SegmentedColorMask ?? false;
+ bool isGray = ibi?.IsGray ?? false;
+ int bitonal = ibi?.Bitonal ?? 0;
+ bool hasAlphaMask = ii.AlphaMaskData != null;
+ bool hasBitmapMask = ii.BitmapMaskData != null;
+
+ bool isFaxEncoding = false;
+ byte[]? imageDataFax = null;
+ int k = 0;
{
FlateDecode fd = new FlateDecode();
+
+ // Color mask: currently disabled.
+ // Color mask.
if (firstMaskColor != -1 && lastMaskColor != -1)
{
// Color mask requires Reader 4.0 or higher.
- if (!segmentedColorMask && pdfVersion >= 13 && !idb.IsGray)
+ if (!segmentedColorMask && pdfVersion >= 13 && !isGray)
{
- PdfArray array = new PdfArray(_document);
+ PdfArray array = new PdfArray(Document);
array.Elements.Add(new PdfInteger(firstMaskColor));
array.Elements.Add(new PdfInteger(lastMaskColor));
Elements[Keys.Mask] = array;
@@ -618,96 +518,117 @@ void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options)
else
{
// Monochrome mask.
- byte[] maskDataCompressed = fd.Encode(idb.BitmapMask, _document.Options.FlateEncodeMode);
- var pdfMask = new PdfDictionary(_document);
+ byte[] maskDataCompressed = fd.Encode(ii.GetPdfBitmapMaskData()!, Document.Options.FlateEncodeMode);
+ var pdfMask = new PdfDictionary(Document);
pdfMask.Elements.SetName(Keys.Type, "/XObject");
pdfMask.Elements.SetName(Keys.Subtype, "/Image");
Owner.IrefTable.Add(pdfMask);
pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
- pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length);
- pdfMask.Elements["/Filter"] = new PdfName("/FlateDecode");
- pdfMask.Elements[Keys.Width] = new PdfInteger((int)ii.Width);
- pdfMask.Elements[Keys.Height] = new PdfInteger((int)ii.Height);
- pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
- pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true);
- Elements[Keys.Mask] = pdfMask.Reference;
+ pdfMask.Elements.SetInteger(PdfStream.Keys.Length, maskDataCompressed.Length);
+ pdfMask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
+ pdfMask.Elements.SetInteger(Keys.Width, ii.PixelWidth);
+ pdfMask.Elements.SetInteger(Keys.Height, ii.PixelHeight);
+ pdfMask.Elements.SetInteger(Keys.BitsPerComponent, 1);
+ pdfMask.Elements.SetBoolean(Keys.ImageMask, true);
+ Elements[Keys.Mask] = pdfMask.RequiredReference;
}
}
if (hasAlphaMask && pdfVersion >= 14)
{
// #PDF-A
- if (_document.IsPdfA)
+ if (Document.IsPdfA)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Alpha mask of PdfImage suppressed.");
}
else
{
// The image provides an alpha mask (requires Arcrobat 5.0 or higher).
- byte[] alphaMaskCompressed = fd.Encode(idb.AlphaMask, _document.Options.FlateEncodeMode);
- var smask = new PdfDictionary(_document);
+ byte[] alphaMaskCompressed = fd.Encode(ii.GetPdfAlphaMaskData()!, Document.Options.FlateEncodeMode);
+ var smask = new PdfDictionary(Document);
smask.Elements.SetName(Keys.Type, "/XObject");
smask.Elements.SetName(Keys.Subtype, "/Image");
Owner.IrefTable.Add(smask);
smask.Stream = new PdfStream(alphaMaskCompressed, smask);
- smask.Elements["/Length"] = new PdfInteger(alphaMaskCompressed.Length);
- smask.Elements["/Filter"] = new PdfName("/FlateDecode");
- smask.Elements[Keys.Width] = new PdfInteger((int)ii.Width);
- smask.Elements[Keys.Height] = new PdfInteger((int)ii.Height);
- smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8);
- smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
- Elements[Keys.SMask] = smask.Reference;
+ smask.Elements.SetInteger(PdfStream.Keys.Length, alphaMaskCompressed.Length);
+ smask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
+ smask.Elements.SetInteger(Keys.Width, ii.PixelWidth);
+ smask.Elements.SetInteger(Keys.Height, ii.PixelHeight);
+ smask.Elements.SetInteger(Keys.BitsPerComponent, 8);
+ smask.Elements.SetName(Keys.ColorSpace, "/DeviceGray");
+ Elements[Keys.SMask] = smask.RequiredReference;
}
}
- byte[] imageDataCompressed = fd.Encode(idb.Data, _document.Options.FlateEncodeMode);
- byte[]? imageDataFaxCompressed = idb.DataFax != null ? fd.Encode(idb.DataFax, _document.Options.FlateEncodeMode) : null;
+ var imageBits = ii.ImageData;
+ var imageData = ii.GetPdfImageData()!;
+ // If fax encoding is allowed, try if fax encoding reduces the size.
+ if (bits == 1 && Document.Options.EnableCcittCompressionForBilevelImages &&
+ ibi != null)
+ {
+ // Only try Group 4 encoding.
+ // It seems that Group 3 2D encoding never beats both other encodings, therefore we don’t call it here.
+
+ byte[] tempG4 = new byte[imageData.Length];
+ int ccittSizeG4 = DoFaxEncodingGroup4(ref tempG4, imageBits, (uint)ibi.BytesFileOffset, (uint)ibi.PixelWidth, (uint)ibi.PixelHeight);
+
+ isFaxEncoding = ccittSizeG4 > 0;
+ if (isFaxEncoding)
+ {
+ Array.Resize(ref tempG4, ccittSizeG4);
+ imageDataFax = tempG4;
+ k = -1;
+ }
+ }
+
+ byte[] imageDataCompressed = fd.Encode(imageData, Document.Options.FlateEncodeMode);
+ byte[]? imageDataFaxCompressed = imageDataFax != null ? fd.Encode(imageDataFax, Document.Options.FlateEncodeMode) : null;
bool usesCcittEncoding = false;
- if (idb.DataFax != null && imageDataFaxCompressed != null &&
- (idb.LengthFax < imageDataCompressed.Length ||
+ if (imageDataFax != null && imageDataFaxCompressed != null &&
+ (imageDataFax.Length < imageDataCompressed.Length ||
imageDataFaxCompressed.Length < imageDataCompressed.Length))
{
// /CCITTFaxDecode creates the smaller file (with or without /FlateDecode).
usesCcittEncoding = true;
- if (idb.LengthFax < imageDataCompressed.Length)
+ if (imageDataFax.Length < imageDataCompressed.Length)
{
- Stream = new PdfStream(idb.DataFax, this);
- Elements["/Length"] = new PdfInteger(idb.LengthFax);
- Elements["/Filter"] = new PdfName("/CCITTFaxDecode");
+ Stream = new PdfStream(imageDataFax, this);
+ Elements["/Length"] = new PdfInteger(imageDataFax.Length);
+ Elements[PdfStream.Keys.Filter] = new PdfName("/CCITTFaxDecode");
var dictionary = new PdfDictionary();
- if (idb.K != 0)
- dictionary.Elements.Add("/K", new PdfInteger(idb.K));
- if (idb.IsBitonal < 0)
+ if (k != 0)
+ dictionary.Elements.Add("/K", new PdfInteger(k));
+ if (bitonal < 0)
dictionary.Elements.Add("/BlackIs1", new PdfBoolean(true));
dictionary.Elements.Add("/EndOfBlock", new PdfBoolean(false));
- dictionary.Elements.Add("/Columns", new PdfInteger((int)ii.Width));
- dictionary.Elements.Add("/Rows", new PdfInteger((int)ii.Height));
+ dictionary.Elements.Add("/Columns", new PdfInteger(ii.PixelWidth));
+ dictionary.Elements.Add("/Rows", new PdfInteger(ii.PixelHeight));
Elements[PdfStream.Keys.DecodeParms] = dictionary;
}
else
{
Stream = new PdfStream(imageDataFaxCompressed, this);
Elements["/Length"] = new PdfInteger(imageDataFaxCompressed.Length);
- var arrayFilters = new PdfArray(_document);
+ var arrayFilters = new PdfArray(Document);
arrayFilters.Elements.Add(new PdfName("/FlateDecode"));
arrayFilters.Elements.Add(new PdfName("/CCITTFaxDecode"));
- Elements["/Filter"] = arrayFilters;
- var arrayDecodeParms = new PdfArray(_document);
+ Elements[PdfStream.Keys.Filter] = arrayFilters;
+ var arrayDecodeParms = new PdfArray(Document);
var dictFlateDecodeParms = new PdfDictionary();
var dictCcittFaxDecodeParms = new PdfDictionary();
- if (idb.K != 0)
- dictCcittFaxDecodeParms.Elements.Add("/K", new PdfInteger(idb.K));
- if (idb.IsBitonal < 0)
+ if (k != 0)
+ dictCcittFaxDecodeParms.Elements.Add("/K", new PdfInteger(k));
+ if (bitonal < 0)
dictCcittFaxDecodeParms.Elements.Add("/BlackIs1", new PdfBoolean(true));
dictCcittFaxDecodeParms.Elements.Add("/EndOfBlock", new PdfBoolean(false));
- dictCcittFaxDecodeParms.Elements.Add("/Columns", new PdfInteger((int)ii.Width));
- dictCcittFaxDecodeParms.Elements.Add("/Rows", new PdfInteger((int)ii.Height));
+ dictCcittFaxDecodeParms.Elements.Add("/Columns", new PdfInteger(ii.PixelWidth));
+ dictCcittFaxDecodeParms.Elements.Add("/Rows", new PdfInteger(ii.PixelHeight));
arrayDecodeParms.Elements.Add(dictFlateDecodeParms); // How to add the "null object"?
arrayDecodeParms.Elements.Add(dictCcittFaxDecodeParms);
@@ -718,38 +639,39 @@ void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options)
{
// /FlateDecode creates the smaller file (or no monochrome bitmap).
Stream = new PdfStream(imageDataCompressed, this);
- Elements["/Length"] = new PdfInteger(imageDataCompressed.Length);
- Elements["/Filter"] = new PdfName("/FlateDecode");
+ Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length);
+ Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
}
- Elements[Keys.Width] = new PdfInteger((int)ii.Width);
- Elements[Keys.Height] = new PdfInteger((int)ii.Height);
+ Elements[Keys.Width] = new PdfInteger(ii.PixelWidth);
+ Elements[Keys.Height] = new PdfInteger(ii.PixelHeight);
Elements[Keys.BitsPerComponent] = new PdfInteger(bits);
// Anything needed for CMYK? Do we have sample images?
// CCITT encoding: we need color palette for isBitonal == 0.
// FlateDecode: we need color palette for isBitonal <= 0 unless we have grayscales.
- if ((usesCcittEncoding && idb.IsBitonal == 0) ||
- (!usesCcittEncoding && idb.IsBitonal <= 0 && !idb.IsGray))
+ if ((usesCcittEncoding && bitonal == 0) ||
+ (!usesCcittEncoding && bitonal <= 0 && !isGray))
{
- var colorPalette = new PdfDictionary(_document);
- byte[]? packedPaletteData = idb.PaletteDataLength >= 48 ? fd.Encode(idb.PaletteData, _document.Options.FlateEncodeMode) : null; // Don’t compress small palettes.
- if (packedPaletteData != null && packedPaletteData.Length + 20 < idb.PaletteDataLength) // +20: compensate for the overhead (estimated value).
+ var colorPalette = new PdfDictionary(Document);
+ var paletteData = ii.GetPdfPaletteData();
+ byte[]? packedPaletteData = paletteData != null && paletteData!.Length >= 48 ? fd.Encode(paletteData, Document.Options.FlateEncodeMode) : null; // Don’t compress small palettes.
+ if (packedPaletteData != null && packedPaletteData.Length + 20 < paletteData!.Length) // +20: compensate for the overhead (estimated value).
{
// Create compressed color palette.
colorPalette.CreateStream(packedPaletteData);
- colorPalette.Elements["/Length"] = new PdfInteger(packedPaletteData.Length);
- colorPalette.Elements["/Filter"] = new PdfName("/FlateDecode");
+ colorPalette.Elements.SetInteger(PdfStream.Keys.Length, packedPaletteData.Length);
+ colorPalette.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
}
else
{
// Create uncompressed color palette.
- colorPalette.CreateStream(idb.PaletteData);
- colorPalette.Elements["/Length"] = new PdfInteger(idb.PaletteDataLength);
+ colorPalette.CreateStream(paletteData!);
+ colorPalette.Elements.SetInteger(PdfStream.Keys.Length, paletteData!.Length);
}
Owner.IrefTable.Add(colorPalette);
- var arrayColorSpace = new PdfArray(_document);
+ var arrayColorSpace = new PdfArray(Document);
arrayColorSpace.Elements.Add(new PdfName("/Indexed"));
arrayColorSpace.Elements.Add(new PdfName("/DeviceRGB"));
arrayColorSpace.Elements.Add(new PdfInteger((int)ii.ColorsUsed - 1));
@@ -760,10 +682,11 @@ void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options)
{
Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
}
- if (_image.Interpolate)
+
+ if (_image is { Interpolate: true })
{
// #PDF-A
- if (_document.IsPdfA)
+ if (Document.IsPdfA)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Image interpolation suppressed.");
}
@@ -775,76 +698,78 @@ void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options)
}
}
- void CreateTrueColorMemoryBitmap(int components, int bits, bool hasAlpha, PdfDocumentOptions options)
+ void CreateTrueColorMemoryBitmap(int components, int bits, bool hasAlpha)
{
- // TODO_OLD Use hasAlpha? Or is ot superfluous?
+ // TODO_OLD Use hasAlpha? Or is it superfluous?
int pdfVersion = Owner.Version;
var fd = new FlateDecode();
- var idb = (ImageDataBitmap?)_image._importedImage?.ImageData(options) ?? NRT.ThrowOnNull();
- var ii = _image._importedImage?.Information ?? NRT.ThrowOnNull();
- bool hasMask = idb.AlphaMaskLength > 0 || idb.BitmapMaskLength > 0;
- bool hasAlphaMask = idb.AlphaMaskLength > 0;
+ var ii = (ImportedRasterImage)ImportedImage!;
+ var ibi = ii as ImportedBmpImage;
+ bool hasAlphaMask = ii.AlphaMaskData != null;
+ bool hasBitmapMask = ii.BitmapMaskData != null;
+ bool hasMask = hasAlphaMask || hasBitmapMask;
- if (hasMask && idb.BitmapMask != null!)
+ if (hasMask && hasBitmapMask)
{
// Monochrome mask is either sufficient or
// provided for compatibility with older reader versions.
- byte[] maskDataCompressed = fd.Encode(idb.BitmapMask, _document.Options.FlateEncodeMode);
- var pdfMask = new PdfDictionary(_document);
+ byte[] maskDataCompressed = fd.Encode(ii.GetPdfBitmapMaskData()!, Document.Options.FlateEncodeMode);
+ var pdfMask = new PdfDictionary(Document);
pdfMask.Elements.SetName(Keys.Type, "/XObject");
pdfMask.Elements.SetName(Keys.Subtype, "/Image");
Owner.IrefTable.Add(pdfMask);
pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
- pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length);
- pdfMask.Elements["/Filter"] = new PdfName("/FlateDecode");
- pdfMask.Elements[Keys.Width] = new PdfInteger((int)ii.Width);
- pdfMask.Elements[Keys.Height] = new PdfInteger((int)ii.Height);
+ pdfMask.Elements.SetInteger(PdfStream.Keys.Length, maskDataCompressed.Length);
+ pdfMask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
+ pdfMask.Elements[Keys.Width] = new PdfInteger(ii.PixelWidth);
+ pdfMask.Elements[Keys.Height] = new PdfInteger(ii.PixelHeight);
pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true);
- Elements[Keys.Mask] = pdfMask.Reference;
+ Elements[Keys.Mask] = pdfMask.RequiredReference;
}
if (hasMask && hasAlphaMask && pdfVersion >= 14)
{
// #PDF-A
- if (_document.IsPdfA)
+ if (Document.IsPdfA)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Alpha mask of PdfImage suppressed.");
}
else
{
// The image provides an alpha mask (requires Acrobat 5.0 or higher).
- byte[] alphaMaskCompressed = fd.Encode(idb.AlphaMask, _document.Options.FlateEncodeMode);
- var smask = new PdfDictionary(_document);
+ byte[] alphaMaskCompressed = fd.Encode(ii.GetPdfAlphaMaskData()!, Document.Options.FlateEncodeMode);
+ var smask = new PdfDictionary(Document);
smask.Elements.SetName(Keys.Type, "/XObject");
smask.Elements.SetName(Keys.Subtype, "/Image");
Owner.IrefTable.Add(smask);
smask.Stream = new PdfStream(alphaMaskCompressed, smask);
- smask.Elements["/Length"] = new PdfInteger(alphaMaskCompressed.Length);
- smask.Elements["/Filter"] = new PdfName("/FlateDecode");
- smask.Elements[Keys.Width] = new PdfInteger((int)ii.Width);
- smask.Elements[Keys.Height] = new PdfInteger((int)ii.Height);
+ smask.Elements.SetInteger(PdfStream.Keys.Length, alphaMaskCompressed.Length);
+ smask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
+ smask.Elements[Keys.Width] = new PdfInteger(ii.PixelWidth);
+ smask.Elements[Keys.Height] = new PdfInteger(ii.PixelHeight);
smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8);
smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
- Elements[Keys.SMask] = smask.Reference;
+ Elements[Keys.SMask] = smask.RequiredReference;
}
}
- byte[] imageDataCompressed = fd.Encode(idb.Data, _document.Options.FlateEncodeMode);
+ byte[] imageDataCompressed = fd.Encode(ii.GetPdfImageData(), Document.Options.FlateEncodeMode);
Stream = new PdfStream(imageDataCompressed, this);
- Elements["/Length"] = new PdfInteger(imageDataCompressed.Length);
- Elements["/Filter"] = new PdfName("/FlateDecode");
- Elements[Keys.Width] = new PdfInteger((int)ii.Width);
- Elements[Keys.Height] = new PdfInteger((int)ii.Height);
+ Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length);
+ Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
+ Elements[Keys.Width] = new PdfInteger(ii.PixelWidth);
+ Elements[Keys.Height] = new PdfInteger(ii.PixelHeight);
Elements[Keys.BitsPerComponent] = new PdfInteger(8);
// Anything needed for CMYK? Do we have sample images?
Elements[Keys.ColorSpace] = new PdfName(components == 1 ? "/DeviceGray" : "/DeviceRGB");
- if (_image.Interpolate)
- {
+
+ if (_image is { Interpolate: true })
+ {
// #PDF-A
- if (_document.IsPdfA)
+ if (Document.IsPdfA)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Image interpolation suppressed.");
}
@@ -886,20 +811,22 @@ static int ReadDWord(ReadOnlySpan bytes, int offset)
/// true (ARGB), false (RGB)
void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha)
{
+ // Only called for GDI and WPF.
+ // _image is not null here.
+
//#if DEBUG_
// image.image.Save("$$$.bmp", ImageFormat.Bmp);
//#endif
int pdfVersion = Owner.Version;
var memory = new MemoryStream();
#if GDI
- _image._gdiImage.Save(memory, ImageFormat.Bmp);
+ _image!._gdiImage.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
#endif
#if WPF
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
- encoder.Frames.Add(BitmapFrame.Create(_image._wpfImage));
+ encoder.Frames.Add(BitmapFrame.Create(_image!._wpfImage));
encoder.Save(memory);
#endif
- // Idea: Use ImageImporterBMP here to avoid redundant code.
int streamLength = (int)memory.Length;
Debug.Assert(streamLength > 0, "Bitmap image encoding failed.");
@@ -907,7 +834,7 @@ void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha)
{
#if !WUI
// Available with wrt, but not with wrt81.
- // Note: imageBits.Length can be larger than streamLength. Do not use these extra bytes!
+ // Note that imageBits.Length can be larger than streamLength. Do not use these extra bytes.
byte[] imageBits = memory.GetBuffer();
#else
byte[] imageBits = new byte[streamLength];
@@ -916,152 +843,86 @@ void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha)
memory.Dispose();
#endif
- int height = _image.PixelHeight;
- int width = _image.PixelWidth;
+ int height = _image!.PixelHeight;
+ int width = _image!.PixelWidth;
- // We could define structures for
- // BITMAPFILEHEADER
- // { BITMAPINFO }
- // BITMAPINFOHEADER
- // to avoid ReadWord and ReadDWord ... (but w/o pointers this doesn’t help much)
-
- if (ReadWord(imageBits, 0) != 0x4d42 || // "BM"
- ReadDWord(imageBits, 2) != streamLength ||
- ReadDWord(imageBits, 14) != 40 || // sizeof BITMAPINFOHEADER
- ReadDWord(imageBits, 18) != width ||
- ReadDWord(imageBits, 22) != height)
- {
- throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format");
- }
- if (ReadWord(imageBits, 26) != 1 ||
- (!hasAlpha && ReadWord(imageBits, 28) != components * bits ||
- hasAlpha && ReadWord(imageBits, 28) != (components + 1) * bits) ||
- ReadDWord(imageBits, 30) != 0)
- {
- throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format #2");
- }
+ // We use TryImportImageBmp here to avoid redundant code.
- int nFileOffset = ReadDWord(imageBits, 10);
- int logicalComponents = components;
- if (components == 4)
- logicalComponents = 3;
+ var ii = ToBeNamed.TryImportImageBmp(imageBits, out var image);
+ if (!ii)
+ throw new NotSupportedException("ReadTrueColorMemoryBitmap: unsupported format");
+ var bmpImage = image as ImportedBmpImage;
+ if (bmpImage == null)
+ throw new NotSupportedException("ReadTrueColorMemoryBitmap: unsupported format");
- byte[] imageData = new byte[components * width * height];
-
- bool hasMask = false;
- bool hasAlphaMask = false;
- byte[]? alphaMask = hasAlpha ? new byte[width * height] : null;
- MonochromeMask? mask = hasAlpha ? new MonochromeMask(width, height) : null;
-
- int nOffsetRead = 0;
- if (logicalComponents == 3)
- {
- Debug.Assert(mask != null || (mask == null && !hasAlpha));
- for (int y = 0; y < height; ++y)
- {
- int nOffsetWrite = 3 * (height - 1 - y) * width;
- int nOffsetWriteAlpha = 0;
- if (hasAlpha)
- {
- Debug.Assert(mask != null);
- mask.StartLine(y);
- nOffsetWriteAlpha = (height - 1 - y) * width;
- }
-
- for (int x = 0; x < width; ++x)
- {
- imageData[nOffsetWrite] = imageBits[nFileOffset + nOffsetRead + 2];
- imageData[nOffsetWrite + 1] = imageBits[nFileOffset + nOffsetRead + 1];
- imageData[nOffsetWrite + 2] = imageBits[nFileOffset + nOffsetRead];
- if (hasAlpha && alphaMask != null)
- {
- Debug.Assert(mask != null);
- mask.AddPel(imageBits[nFileOffset + nOffsetRead + 3]);
- alphaMask[nOffsetWriteAlpha] = imageBits[nFileOffset + nOffsetRead + 3];
- if (!hasMask || !hasAlphaMask)
- {
- if (imageBits[nFileOffset + nOffsetRead + 3] != 255)
- {
- hasMask = true;
- if (imageBits[nFileOffset + nOffsetRead + 3] != 0)
- hasAlphaMask = true;
- }
- }
- ++nOffsetWriteAlpha;
- }
- nOffsetRead += hasAlpha ? 4 : components;
- nOffsetWrite += 3;
- }
- nOffsetRead = 4 * ((nOffsetRead + 3) / 4); // Align to 32 bit boundary.
- }
- }
- else if (components == 1)
- {
- // Grayscale
- throw new NotImplementedException("Image format not supported (grayscales).");
- }
+ var imageData = bmpImage!.GetPdfImageData();
+ var hasAlphaMask = bmpImage.AlphaMaskData != null;
+ var hasMask = bmpImage.BitmapMaskData != null;
+ var alphaMask = bmpImage.GetPdfAlphaMaskData();
+ var mask = bmpImage.GetPdfBitmapMask();
var fd = new FlateDecode();
if (hasMask && mask != null && mask.MaskUsed)
{
// Monochrome mask is either sufficient or
// provided for compatibility with older reader versions.
- byte[] maskDataCompressed = fd.Encode(mask.MaskData, _document.Options.FlateEncodeMode);
- var pdfMask = new PdfDictionary(_document);
+ byte[] maskDataCompressed = fd.Encode(mask.MaskData, Document.Options.FlateEncodeMode);
+ var pdfMask = new PdfDictionary(Document);
pdfMask.Elements.SetName(Keys.Type, "/XObject");
pdfMask.Elements.SetName(Keys.Subtype, "/Image");
Owner.IrefTable.Add(pdfMask);
pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
- pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length);
- pdfMask.Elements["/Filter"] = new PdfName("/FlateDecode");
+ pdfMask.Elements.SetInteger(PdfStream.Keys.Length, maskDataCompressed.Length);
+ pdfMask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
pdfMask.Elements[Keys.Width] = new PdfInteger(width);
pdfMask.Elements[Keys.Height] = new PdfInteger(height);
pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true);
- Elements[Keys.Mask] = pdfMask.Reference;
+ Elements[Keys.Mask] = pdfMask.RequiredReference;
}
- if (hasMask && hasAlphaMask && pdfVersion >= 14 && alphaMask != null)
+ if (/*hasMask &&*/ hasAlphaMask && pdfVersion >= 14 && alphaMask != null)
{
// #PDF-A
- if (_document.IsPdfA)
+ if (Document.IsPdfA)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Alpha mask of PdfImage suppressed.");
}
else
{
// The image provides an alpha mask (requires Acrobat 5.0 or higher).
- byte[] alphaMaskCompressed = fd.Encode(alphaMask, _document.Options.FlateEncodeMode);
- var smask = new PdfDictionary(_document);
+ byte[] alphaMaskCompressed = fd.Encode(alphaMask, Document.Options.FlateEncodeMode);
+ var smask = new PdfDictionary(Document);
smask.Elements.SetName(Keys.Type, "/XObject");
smask.Elements.SetName(Keys.Subtype, "/Image");
Owner.IrefTable.Add(smask);
smask.Stream = new PdfStream(alphaMaskCompressed, smask);
- smask.Elements[PdfStream.Keys.Length] = new PdfInteger(alphaMaskCompressed.Length);
- smask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
+ smask.Elements.SetInteger(PdfStream.Keys.Length, alphaMaskCompressed.Length);
+ smask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
smask.Elements[Keys.Width] = new PdfInteger(width);
smask.Elements[Keys.Height] = new PdfInteger(height);
smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8);
smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
- Elements[Keys.SMask] = smask.Reference;
+ Elements[Keys.SMask] = smask.RequiredReference;
}
}
- byte[] imageDataCompressed = fd.Encode(imageData, _document.Options.FlateEncodeMode);
+ byte[] imageDataCompressed = fd.Encode(imageData, Document.Options.FlateEncodeMode);
Stream = new PdfStream(imageDataCompressed, this);
- Elements["/Length"] = new PdfInteger(imageDataCompressed.Length);
- Elements["/Filter"] = new PdfName("/FlateDecode");
+ Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length);
+ Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
Elements[Keys.Width] = new PdfInteger(width);
Elements[Keys.Height] = new PdfInteger(height);
Elements[Keys.BitsPerComponent] = new PdfInteger(8);
// Anything needed for CMYK? Do we have sample images?
Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB");
+
if (_image.Interpolate)
- {
+ {
// #PDF-A
- if (_document.IsPdfA)
+ if (Document.IsPdfA)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Image interpolation suppressed.");
}
@@ -1073,264 +934,93 @@ void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha)
}
}
- /* BITMAPINFOHEADER struct and byte offsets:
- typedef struct tagBITMAPINFOHEADER{
- DWORD biSize; // 14
- LONG biWidth; // 18
- LONG biHeight; // 22
- WORD biPlanes; // 26
- WORD biBitCount; // 28
- DWORD biCompression; // 30
- DWORD biSizeImage; // 34
- LONG biXPelsPerMeter; // 38
- LONG biYPelsPerMeter; // 42
- DWORD biClrUsed; // 46
- DWORD biClrImportant; // 50
- } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
- */
-
void ReadIndexedMemoryBitmap(int bits)
{
+ // Only called for GDI and WPF.
+ // _image is not null here.
+
int pdfVersion = Owner.Version;
int firstMaskColor = -1, lastMaskColor = -1;
bool segmentedColorMask = false;
MemoryStream memory = new MemoryStream();
#if GDI
- _image._gdiImage.Save(memory, ImageFormat.Bmp);
+ _image!._gdiImage.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
#endif
#if WPF
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
//if (!_image._path.StartsWith("*", StringComparison.Ordinal))
// encoder.Frames.Add(BitmapFrame.Create(new Uri(_image._path), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad));
//else
- encoder.Frames.Add(BitmapFrame.Create(_image._wpfImage));
+ encoder.Frames.Add(BitmapFrame.Create(_image!._wpfImage));
encoder.Save(memory);
#endif
- // Idea: Use ImageImporterBMP here to avoid redundant code.
-
int streamLength = (int)memory.Length;
Debug.Assert(streamLength > 0, "Bitmap image encoding failed.");
if (streamLength > 0)
{
+ int height = _image!.PixelHeight;
+ int width = _image!.PixelWidth;
+
byte[] imageBits = new byte[streamLength];
memory.Seek(0, SeekOrigin.Begin);
- memory.Read(imageBits, 0, streamLength);
+ _ = memory.Read(imageBits, 0, streamLength);
#if !WUI
memory.Close();
#else
memory.Dispose();
#endif
- int height = _image.PixelHeight;
- int width = _image.PixelWidth;
-
- if (ReadWord(imageBits, 0) != 0x4d42 || // "BM"
- ReadDWord(imageBits, 2) != streamLength ||
- ReadDWord(imageBits, 14) != 40 || // sizeof BITMAPINFOHEADER
-#if WPF
- ReadDWord(imageBits, 18) != width ||
- ReadDWord(imageBits, 22) != height)
-#else
- ReadDWord(imageBits, 18) != width ||
- ReadDWord(imageBits, 22) != height)
-#endif
- {
- throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format");
- }
-
- //#if WPF
- // // These two lines should be superfluous, otherwise an exception would have been thrown above.
- // width = ReadDWord(imageBits, 18);
- // height = ReadDWord(imageBits, 22);
- //#endif
-
- int fileBits = ReadWord(imageBits, 28);
- if (fileBits != bits)
- {
- if (fileBits == 1 || fileBits == 4 || fileBits == 8)
- bits = fileBits;
- }
-
- if (ReadWord(imageBits, 26) != 1 ||
- ReadWord(imageBits, 28) != bits ||
- ReadDWord(imageBits, 30) != 0)
- {
- throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #2");
- }
-
- int bytesFileOffset = ReadDWord(imageBits, 10);
- const int bytesColorPaletteOffset = 0x36; // GDI+ always returns Windows bitmaps: sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER
- int paletteColors = ReadDWord(imageBits, 46);
- if ((bytesFileOffset - bytesColorPaletteOffset) / 4 != paletteColors)
- {
- throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3");
- }
-
- MonochromeMask mask = new MonochromeMask(width, height);
-
- bool isGray = bits == 8 && (paletteColors == 256 || paletteColors == 0);
- int isBitonal = 0; // 0: false; >0: true; <0: true (inverted).
- byte[] paletteData = new byte[3 * paletteColors];
- for (int color = 0; color < paletteColors; ++color)
- {
- paletteData[3 * color] = imageBits[bytesColorPaletteOffset + 4 * color + 2];
- paletteData[3 * color + 1] = imageBits[bytesColorPaletteOffset + 4 * color + 1];
- paletteData[3 * color + 2] = imageBits[bytesColorPaletteOffset + 4 * color + 0];
- if (isGray)
- isGray = paletteData[3 * color] == paletteData[3 * color + 1] &&
- paletteData[3 * color] == paletteData[3 * color + 2];
-
- if (imageBits[bytesColorPaletteOffset + 4 * color + 3] < 128)
- {
- // We treat this as transparency.
- if (firstMaskColor == -1)
- firstMaskColor = color;
- if (lastMaskColor == -1 || lastMaskColor == color - 1)
- lastMaskColor = color;
- if (lastMaskColor != color)
- segmentedColorMask = true;
- }
- //else
- //{
- // // We treat this as opacity.
- //}
- }
-
- if (bits == 1)
- {
- if (paletteColors == 0)
- isBitonal = 1;
- if (paletteColors == 2)
- {
- if (paletteData[0] == 0 &&
- paletteData[1] == 0 &&
- paletteData[2] == 0 &&
- paletteData[3] == 255 &&
- paletteData[4] == 255 &&
- paletteData[5] == 255)
- isBitonal = 1; // Black on white
- if (paletteData[5] == 0 &&
- paletteData[4] == 0 &&
- paletteData[3] == 0 &&
- paletteData[2] == 255 &&
- paletteData[1] == 255 &&
- paletteData[0] == 255)
- isBitonal = -1; // White on black
- }
- }
-
- bool hasMask = firstMaskColor != -1 && lastMaskColor != -1;
-
- // NYI: (no sample found where this was required)
- // if (segmentedColorMask = true)
- // { ... }
+ // We use TryImportImageBmp here to avoid redundant code.
bool isFaxEncoding = false;
- byte[] imageData = new byte[((width * bits + 7) / 8) * height];
byte[]? imageDataFax = null;
int k = 0;
- // If fax encoding is allowed, try if fax encoding reduces the size.
- if (bits == 1 && _document.Options.EnableCcittCompressionForBilevelImages)
+ var ii = ToBeNamed.TryImportImageBmp(imageBits, out var image);
+ if (!ii)
+ throw new NotSupportedException("ReadIndexedMemoryBitmap: unsupported format");
+ var bmpImage = image as ImportedBmpImage;
+ if (bmpImage == null)
+ throw new NotSupportedException("ReadIndexedMemoryBitmap: unsupported format");
+ if (bmpImage.PaletteData == null)
+ throw new NotSupportedException("ReadIndexedMemoryBitmap: unsupported format");
+
+ var imageData = bmpImage!.GetPdfImageData();
+ var paletteData = bmpImage.GetPdfPaletteData()!;
+ var paletteColors = bmpImage.PaletteColors;
+ //var hasAlphaMask = bmpImage.AlphaMaskData != null;
+ var hasMask = bmpImage.BitmapMaskData != null;
+ //var alphaMask = bmpImage.GetPdfAlphaMaskData();
+ var mask = bmpImage.GetPdfBitmapMask();
+ var isGray = bmpImage.IsGray;
+ var bytesFileOffset = bmpImage.BytesFileOffset;
+ segmentedColorMask = bmpImage.SegmentedColorMask;
+ firstMaskColor = bmpImage.FirstMaskColor;
+ lastMaskColor = bmpImage.LastMaskColor;
+ hasMask |= firstMaskColor != -1 && lastMaskColor != -1;
+ var isBitonal = bmpImage.Bitonal;
+ if (bits != bmpImage.BitCount)
{
- // Future improvement: flag/option to select a specific format?
- // Is: Only try Group 4 encoding.
-
- //// We try Group 3 1D and Group 4 (2D) encoding here and keep the smaller byte array.
- ////byte[] temp = new byte[imageData.Length];
- ////int ccittSize = DoFaxEncoding(ref temp, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height);
+ if (bmpImage.BitCount == 1 || bmpImage.BitCount == 4 || bmpImage.BitCount == 8)
+ bits = bmpImage.BitCount;
+ }
- //// It seems that Group 3 2D encoding never beats both other encodings, therefore we don’t call it here.
- ////byte[] temp2D = new byte[imageData.Length];
- ////uint dpiY = (uint)image.VerticalResolution;
- ////uint kTmp = 0;
- ////int ccittSize2D = DoFaxEncoding2D((uint)bytesFileOffset, ref temp2D, imageBits, (uint)width, (uint)height, dpiY, out kTmp);
- ////k = (int) kTmp;
+ // If fax encoding is allowed, try if fax encoding reduces the size.
+ if (bits == 1 && Document.Options.EnableCcittCompressionForBilevelImages)
+ {
+ // Only try Group 4 encoding.
+ // It seems that Group 3 2D encoding never beats both other encodings, therefore we don’t call it here.
byte[] tempG4 = new byte[imageData.Length];
int ccittSizeG4 = DoFaxEncodingGroup4(ref tempG4, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height);
- isFaxEncoding = /*ccittSize > 0 ||*/ ccittSizeG4 > 0;
+ isFaxEncoding = ccittSizeG4 > 0;
if (isFaxEncoding)
{
- //if (ccittSize == 0)
- // ccittSize = 0x7fffffff;
- if (ccittSizeG4 == 0)
- ccittSizeG4 = 0x7fffffff;
- //if (ccittSize <= ccittSizeG4)
- //{
- // Array.Resize(ref temp, ccittSize);
- // imageDataFax = temp;
- // k = 0;
- //}
- //else
- {
- Array.Resize(ref tempG4, ccittSizeG4);
- imageDataFax = tempG4;
- k = -1;
- }
- }
- }
-
- //if (hasMask)
- {
- int bytesOffsetRead = 0;
- if (bits == 8 || bits == 4 || bits == 1)
- {
- int bytesPerLine = (width * bits + 7) / 8;
- for (int y = 0; y < height; ++y)
- {
- mask.StartLine(y);
- int bytesOffsetWrite = (height - 1 - y) * ((width * bits + 7) / 8);
- for (int x = 0; x < bytesPerLine; ++x)
- {
- if (isGray)
- {
- // Lookup the gray value from the palette:
- imageData[bytesOffsetWrite] = paletteData[3 * imageBits[bytesFileOffset + bytesOffsetRead]];
- }
- else
- {
- // Store the palette index.
- imageData[bytesOffsetWrite] = imageBits[bytesFileOffset + bytesOffsetRead];
- }
- if (firstMaskColor != -1)
- {
- int n = imageBits[bytesFileOffset + bytesOffsetRead];
- if (bits == 8)
- {
- // TODO_OLD???: segmentedColorMask == true => bad mask NYI
- mask.AddPel((n >= firstMaskColor) && (n <= lastMaskColor));
- }
- else if (bits == 4)
- {
- // TODO_OLD???: segmentedColorMask == true => bad mask NYI
- int n1 = (n & 0xf0) / 16;
- int n2 = (n & 0x0f);
- mask.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor));
- mask.AddPel((n2 >= firstMaskColor) && (n2 <= lastMaskColor));
- }
- else if (bits == 1)
- {
- // TODO_OLD???: segmentedColorMask == true => bad mask NYI
- for (int bit = 1; bit <= 8; ++bit)
- {
- int n1 = (n & 0x80) / 128;
- mask.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor));
- n *= 2;
- }
- }
- }
- bytesOffsetRead += 1;
- bytesOffsetWrite += 1;
- }
- bytesOffsetRead = 4 * ((bytesOffsetRead + 3) / 4); // Align to 32 bit boundary.
- }
- }
- else
- {
- throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3");
+ Array.Resize(ref tempG4, ccittSizeG4);
+ imageDataFax = tempG4;
+ k = -1;
}
}
@@ -1340,7 +1030,7 @@ void ReadIndexedMemoryBitmap(int bits)
// Color mask requires Reader 4.0 or higher.
if (!segmentedColorMask && pdfVersion >= 13 && !isGray)
{
- PdfArray array = new PdfArray(_document);
+ PdfArray array = new PdfArray(Document);
array.Elements.Add(new PdfInteger(firstMaskColor));
array.Elements.Add(new PdfInteger(lastMaskColor));
Elements[Keys.Mask] = array;
@@ -1348,28 +1038,28 @@ void ReadIndexedMemoryBitmap(int bits)
else
{
// Monochrome mask.
- byte[] maskDataCompressed = flateDecode.Encode(mask.MaskData, _document.Options.FlateEncodeMode);
- var pdfMask = new PdfDictionary(_document);
+ byte[] maskDataCompressed = flateDecode.Encode(mask!.MaskData, Document.Options.FlateEncodeMode);
+ var pdfMask = new PdfDictionary(Document);
pdfMask.Elements.SetName(Keys.Type, "/XObject");
pdfMask.Elements.SetName(Keys.Subtype, "/Image");
Owner.IrefTable.Add(pdfMask);
pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
//pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length);
- pdfMask.Elements[PdfStream.Keys.Length] = new PdfInteger(maskDataCompressed.Length);
- pdfMask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
+ pdfMask.Elements.SetInteger(PdfStream.Keys.Length, maskDataCompressed.Length);
+ pdfMask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
pdfMask.Elements[Keys.Width] = new PdfInteger(width);
pdfMask.Elements[Keys.Height] = new PdfInteger(height);
pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true);
- Elements[Keys.Mask] = pdfMask.Reference;
+ Elements[Keys.Mask] = pdfMask.RequiredReference;
}
}
Debug.Assert((isFaxEncoding && imageDataFax != null) || (!isFaxEncoding && imageDataFax == null));
- byte[] imageDataCompressed = flateDecode.Encode(imageData, _document.Options.FlateEncodeMode);
- byte[]? imageDataFaxCompressed = isFaxEncoding ? flateDecode.Encode(imageDataFax!, _document.Options.FlateEncodeMode) : null;
+ byte[] imageDataCompressed = flateDecode.Encode(imageData, Document.Options.FlateEncodeMode);
+ byte[]? imageDataFaxCompressed = isFaxEncoding ? flateDecode.Encode(imageDataFax!, Document.Options.FlateEncodeMode) : null;
bool usesCcittEncoding = false;
if (isFaxEncoding &&
@@ -1402,11 +1092,11 @@ void ReadIndexedMemoryBitmap(int bits)
Stream = new PdfStream(imageDataFaxCompressed, this);
Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataFaxCompressed.Length);
- var arrayFilters = new PdfArray(_document);
+ var arrayFilters = new PdfArray(Document);
arrayFilters.Elements.Add(new PdfName("/FlateDecode"));
arrayFilters.Elements.Add(new PdfName("/CCITTFaxDecode"));
Elements[PdfStream.Keys.Filter] = arrayFilters;
- var arrayDecodeParms = new PdfArray(_document);
+ var arrayDecodeParms = new PdfArray(Document);
var dictFlateDecodeParms = new PdfDictionary();
var dictCcittFaxDecodeParms = new PdfDictionary();
@@ -1427,8 +1117,8 @@ void ReadIndexedMemoryBitmap(int bits)
{
// /FlateDecode creates the smaller file (or no monochrome bitmap).
Stream = new PdfStream(imageDataCompressed, this);
- Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataCompressed.Length);
- Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
+ Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length);
+ Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
}
Elements[Keys.Width] = new PdfInteger(width);
@@ -1441,14 +1131,14 @@ void ReadIndexedMemoryBitmap(int bits)
if ((usesCcittEncoding && isBitonal == 0) ||
(!usesCcittEncoding && isBitonal <= 0 && !isGray))
{
- var colorPalette = new PdfDictionary(_document);
- byte[]? packedPaletteData = paletteData.Length >= 48 ? flateDecode.Encode(paletteData, _document.Options.FlateEncodeMode) : null; // Don’t compress small palettes.
+ var colorPalette = new PdfDictionary(Document);
+ byte[]? packedPaletteData = paletteData.Length >= 48 ? flateDecode.Encode(paletteData, Document.Options.FlateEncodeMode) : null; // Don’t compress small palettes.
if (packedPaletteData != null && packedPaletteData.Length + 20 < paletteData.Length) // +20: compensate for the overhead (estimated value).
{
// Create compressed color palette.
colorPalette.CreateStream(packedPaletteData);
- colorPalette.Elements[PdfStream.Keys.Length] = new PdfInteger(packedPaletteData.Length);
- colorPalette.Elements["/Filter"] = new PdfName("/FlateDecode");
+ colorPalette.Elements.SetInteger(PdfStream.Keys.Length, packedPaletteData.Length);
+ colorPalette.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
}
else
{
@@ -1458,7 +1148,7 @@ void ReadIndexedMemoryBitmap(int bits)
}
Owner.IrefTable.Add(colorPalette);
- var arrayColorSpace = new PdfArray(_document);
+ var arrayColorSpace = new PdfArray(Document);
arrayColorSpace.Elements.Add(new PdfName("/Indexed"));
arrayColorSpace.Elements.Add(new PdfName("/DeviceRGB"));
arrayColorSpace.Elements.Add(new PdfInteger(paletteColors - 1));
@@ -1469,10 +1159,11 @@ void ReadIndexedMemoryBitmap(int bits)
{
Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
}
+
if (_image.Interpolate)
{
// #PDF-A
- if (_document.IsPdfA)
+ if (Document.IsPdfA)
{
PdfSharpLogHost.Logger.LogWarning("PDF/A: Image interpolation suppressed.");
}
@@ -1671,92 +1362,4 @@ void ReadIndexedMemoryBitmap(int bits)
// ReSharper restore InconsistentNaming
}
}
-
- ///
- /// Helper class for creating bitmap masks (8 pels per byte).
- ///
- class MonochromeMask
- {
- ///
- /// Returns the bitmap mask that will be written to PDF.
- ///
- public byte[] MaskData => _maskData;
-
- readonly byte[] _maskData;
-
- ///
- /// Indicates whether the mask has transparent pels.
- ///
- public bool MaskUsed => _used;
-
- ///
- /// Creates a bitmap mask.
- ///
- public MonochromeMask(int sizeX, int sizeY)
- {
- _sizeX = sizeX;
- _sizeY = sizeY;
- int byteSize = ((sizeX + 7) / 8) * sizeY;
- _maskData = new byte[byteSize];
- StartLine(0);
- }
-
- ///
- /// Starts a new line.
- ///
- public void StartLine(int newCurrentLine)
- {
- _bitsWritten = 0;
- _byteBuffer = 0;
- _writeOffset = ((_sizeX + 7) / 8) * (_sizeY - 1 - newCurrentLine);
- }
-
- ///
- /// Adds a pel to the current line.
- ///
- ///
- public void AddPel(bool isTransparent)
- {
- if (_bitsWritten < _sizeX)
- {
- // Mask: 0: opaque, 1: transparent (default mapping)
- if (isTransparent)
- {
- _byteBuffer = (_byteBuffer << 1) + 1;
- _used = true;
- }
- else
- _byteBuffer = _byteBuffer << 1;
- ++_bitsWritten;
- if ((_bitsWritten & 7) == 0)
- {
- _maskData[_writeOffset] = (byte)_byteBuffer;
- ++_writeOffset;
- _byteBuffer = 0;
- }
- else if (_bitsWritten == _sizeX)
- {
- int n = 8 - (_bitsWritten & 7);
- _byteBuffer = _byteBuffer << n;
- _maskData[_writeOffset] = (byte)_byteBuffer;
- }
- }
- }
-
- ///
- /// Adds a pel from an alpha mask value.
- ///
- public void AddPel(int shade)
- {
- // NYI: dithering.
- AddPel(shade < 128);
- }
-
- readonly int _sizeX;
- readonly int _sizeY;
- int _writeOffset;
- int _byteBuffer;
- int _bitsWritten;
- bool _used;
- }
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs
index fc1928f9..c3e3afdb 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs
@@ -1,9 +1,11 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using PdfSharp.Drawing;
-using PdfSharp.Drawing.Internal;
using System.Runtime.InteropServices;
+using PdfSharp.Drawing;
+#if CORE
+using PdfSharp.Internal.Imaging;
+#endif
using System.Security.Cryptography;
using System.Text;
@@ -43,6 +45,23 @@ public PdfImage GetImage(XImage image)
return pdfImage;
}
+#if CORE
+ public PdfImage GetImage(ImportedImage importedImage)
+ {
+ byte[] bytes = importedImage.ImageData;
+
+ var selector = new ImageSelector(bytes);
+
+ if (!_images.TryGetValue(selector, out var pdfImage))
+ {
+ pdfImage = new(Owner, importedImage);
+ Debug.Assert(pdfImage.Owner == Owner);
+ _images[selector] = pdfImage;
+ }
+ return pdfImage;
+ }
+#endif
+
///
/// Map from ImageSelector to PdfImage.
///
@@ -64,39 +83,14 @@ public ImageSelector(XImage image, PdfDocumentOptions options)
// 3. Otherwise, create a GUID.
// TODO_OLD: Create hashes also for other image sources.
var selector = image._path;
-#if true
if (image._path == null! || image._importedImage != null)
{
if (image._importedImage != null)
{
- var iid = image._importedImage.ImageData(options);
- if (iid is ImageDataDct jpeg)
- {
- var hashCreator = GetHashCreator();
- var hash = hashCreator.ComputeHash(jpeg.Data, 0, jpeg.Length);
- selector = GetHashSelectorPrefix(image) + HashToString(hash);
- }
- else if (iid is ImageDataBitmap bmp)
- {
- var hashCreator = GetHashCreator();
- var hash = hashCreator.ComputeHash(bmp.Data, 0, bmp.Length);
- if (bmp.AlphaMask != null! && bmp.AlphaMaskLength > 0)
- {
- var hash2 = hashCreator.ComputeHash(bmp.AlphaMask, 0, bmp.AlphaMaskLength);
- selector = GetHashSelectorPrefix(image) + HashToString(hash) + HashToString(hash2);
- }
- else if (bmp.BitmapMask != null! && bmp.BitmapMaskLength > 0)
- {
- var hash2 = hashCreator.ComputeHash(bmp.BitmapMask, 0, bmp.BitmapMaskLength);
- selector = GetHashSelectorPrefix(image) + HashToString(hash) + HashToString(hash2);
- }
- else
- {
- selector = GetHashSelectorPrefix(image) + HashToString(hash);
- }
- }
- else
- selector = "*" + Guid.NewGuid().ToString("B");
+ var iid2 = image._importedImage.ImageData;
+ var hashCreator = GetHashCreator();
+ var hash = hashCreator.ComputeHash(iid2, 0, iid2.Length);
+ selector = GetHashSelectorPrefix(image) + HashToString(hash);
}
else if (image._stream != null!)
{
@@ -156,19 +150,27 @@ public ImageSelector(XImage image, PdfDocumentOptions options)
// Path must be set. Leading '*' indicates pseudo-paths.
if (image._path == null!)
image._path = Path;
-#else
- // HACK_OLD: implement a way to identify images when they are reused
- if (image._path == null!)
- image._path = "*" + Guid.NewGuid().ToString("B");
-#endif
+
+ }
+
+ /////
+ ///// Initializes a new instance of ImageSelector from an XImage.
+ /////
+ public ImageSelector(byte[] bytes)
+ {
+ var hashCreator = GetHashCreator();
+ var hash = hashCreator.ComputeHash(bytes, 0, bytes.Length);
+ var selector = HashToString(hash);
+
+ Path = GetHashSelectorPrefix() + selector;
}
///
- /// Creates an instance of HashAlgorithm fr use in ImageSelector.
+ /// Creates an instance of HashAlgorithm for use in ImageSelector.
///
- private HashAlgorithm GetHashCreator()
+ HashAlgorithm GetHashCreator()
{
- // Note: Beginning with PDFsharp 6.2.0 Preview 2, we use SHA1 here.
+ // Note hat beginning with PDFsharp 6.2.0 Preview 2, we use SHA1 here.
// Earlier versions used MD5.
// MD5 is deprecated and not FIPS compliant, so we decided to avoid MD5 here.
// SHA1 is not deprecated and similarly efficient as MD5.
@@ -182,12 +184,21 @@ private HashAlgorithm GetHashCreator()
/// Image selectors that are no path names start with an asterisk.
/// We combine image dimensions with the hashcode of the image bits to reduce chance af ambiguity.
///
- private string GetHashSelectorPrefix(XImage image)
+ string GetHashSelectorPrefix(XImage image)
{
return "*hash:" + image.PixelWidth + ':' + image.PixelHeight + ':';
}
- private String HashToString(Byte[] hash)
+ ///
+ /// Image selectors that are no path names start with an asterisk.
+ /// We combine image dimensions with the hashcode of the image bits to reduce chance af ambiguity.
+ ///
+ string GetHashSelectorPrefix()
+ {
+ return "*hash:";
+ }
+
+ string HashToString(byte[] hash)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
@@ -197,7 +208,7 @@ private String HashToString(Byte[] hash)
return sb.ToString();
}
- public string Path { get; }
+ public string Path { get; } // string Selection Key
public override bool Equals(object? obj)
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImportedObjectTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImportedObjectTable.cs
index 26ca154f..92b9af25 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImportedObjectTable.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImportedObjectTable.cs
@@ -7,7 +7,7 @@ namespace PdfSharp.Pdf.Advanced
/// Represents the imported objects of an external document. Used to cache objects that are
/// already imported when a PdfFormXObject is added to a page.
///
- sealed class PdfImportedObjectTable
+ sealed class PdfImportedObjectTable // TODO review
{
///
/// Initializes a new instance of this class with the document the objects are imported from.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNameDictionary.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNameDictionary.cs
index 760122f3..1f819d4d 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNameDictionary.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNameDictionary.cs
@@ -1,10 +1,13 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal;
+using PdfSharp.Pdf.Attachments;
+
namespace PdfSharp.Pdf.Advanced
{
///
- /// Represents the name dictionary.
+ /// Represents the name dictionary of the catalog.
///
public sealed class PdfNameDictionary : PdfDictionary
{
@@ -12,16 +15,25 @@ public sealed class PdfNameDictionary : PdfDictionary
/// Initializes a new instance of the class.
///
public PdfNameDictionary(PdfDocument document)
- : base(document)
- { }
+ : base(document, true)
+ {
+ // Is always an indirect object.
+ //document.Internals.AddObject(this);
+ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfNameDictionary(PdfDictionary dictionary)
: base(dictionary)
{
var dests = Elements.GetDictionary(Keys.Dests);
if (dests != null)
{
- _dests = new PdfNameTreeNode(dests);
+ if (dests is not PdfNameTreeNode dests2)
+ dests2 = new PdfNameTreeNode(dests);
+ _dests = dests2;
}
}
@@ -59,31 +71,44 @@ internal void AddNamedDestination(string destinationName, int destinationPage, P
_dests.AddName(destinationName, destinationDict.Reference);
#endif
}
-
PdfNameTreeNode? _dests;
- internal void AddEmbeddedFile(string name, Stream stream)
- {
- if (_embeddedFiles == null)
- {
- _embeddedFiles = new PdfNameTreeNode();
- Owner.Internals.AddObject(_embeddedFiles);
- Elements.SetReference(Keys.EmbeddedFiles, _embeddedFiles.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull());
- }
+ //// TODO: Remove
+ //internal void AddEmbeddedFile(string name, Stream stream, out PdfFileSpecification fileSpecification, string? subType = null)
+ //{
+ // var embeddedFiles = GetEmbeddedFiles();
+ // if (embeddedFiles == null)
+ // {
+ // // Create a direct object.
+ // embeddedFiles = new PdfEmbeddedFiles();
+ // Elements.Add(Keys.EmbeddedFiles, embeddedFiles);
+ // }
- var embeddedFileStream = new PdfEmbeddedFileStream(Owner, stream);
- var fileSpecification = new PdfFileSpecification(Owner, embeddedFileStream, name);
- Owner.Internals.AddObject(fileSpecification);
+ // var embeddedFileStream = new PdfEmbeddedFileStream(Owner, stream, subType);
+ // fileSpecification = new PdfFileSpecification(Owner, embeddedFileStream, name);
+ // Owner.Internals.AddObject(fileSpecification);
- _embeddedFiles.AddName(name, fileSpecification.ReferenceNotNull);
- }
+ // embeddedFiles.AddName(name, fileSpecification.RequiredReference);
+ //}
+
+ internal bool HasEmbeddedFiles
+ => Elements.TryGetValue(Keys.EmbeddedFiles, out _);
- PdfNameTreeNode? _embeddedFiles;
+ ///
+ /// Get or created... TODO
+ ///
+ [return: NotNullIfNotNull(nameof(create))]
+ internal PdfEmbeddedFiles? GetEmbeddedFiles(bool create = false) // TODO: null or not null? => Use NotNullIfNotNullAttribute
+ {
+ var ef = Elements.GetRequiredDictionary(Keys.EmbeddedFiles,
+ create ? VCF.Create : VCF.None);
+ return ef;
+ }
///
/// Predefined keys of this dictionary.
///
- internal sealed class Keys : KeysBase
+ public sealed class Keys : KeysBase
{
// ReSharper disable InconsistentNaming
@@ -139,7 +164,7 @@ internal sealed class Keys : KeysBase
/// (Optional; PDF 1.4) A name tree mapping name strings to file specifications for embedded file streams
/// (see Section 3.10.3, “Embedded File Streams”).
///
- [KeyInfo("1.4", KeyType.NameTree | KeyType.Optional)]
+ [KeyInfo("1.4", KeyType.NameTree | KeyType.Optional, typeof(PdfEmbeddedFiles))]
public const string EmbeddedFiles = "/EmbeddedFiles";
/////
@@ -157,7 +182,19 @@ internal sealed class Keys : KeysBase
//public const string Renditions = "/Renditions";
// ReSharper restore InconsistentNaming
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+
}
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinationParameters.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinationParameters.cs
index 1f06400b..045fedf1 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinationParameters.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinationParameters.cs
@@ -20,7 +20,7 @@ public class PdfNamedDestinationParameters
static PdfNamedDestinationParameters CreateXYZ(double? left, double? top, double? zoom)
{
- return new PdfNamedDestinationParameters(Format("/XYZ {0} {1} {2}", left, top, zoom));
+ return new(Format("/XYZ {0} {1} {2}", left, top, zoom));
}
///
@@ -170,7 +170,6 @@ static string Format(string format, params double?[] values)
{
objValues[i] = values[i] ?? (object)"null";
}
-
return PdfEncoders.Format(format, objValues);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinations.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinations.cs
index 6e6f809a..53a36cd3 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinations.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinations.cs
@@ -2,7 +2,6 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Pdf.Actions;
-using System.Xml.Linq;
namespace PdfSharp.Pdf.Advanced
{
@@ -12,13 +11,15 @@ namespace PdfSharp.Pdf.Advanced
public sealed class PdfNamedDestinations : PdfDictionary
{
internal PdfNamedDestinations()
- {
- }
+ { }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfNamedDestinations(PdfDictionary dict)
: base(dict)
- {
- }
+ { }
///
/// Gets all the destination names.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs
index 76775b86..a4ed1a33 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs
@@ -1,6 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
using PdfSharp.Pdf.IO;
namespace PdfSharp.Pdf.Advanced
@@ -28,16 +30,28 @@ public PdfObjectStream(PdfDocument document)
}
///
- /// Initializes a new instance from an existing dictionary. Used for object type transformation.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfObjectStream(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
internal PdfObjectStream(PdfDictionary dict, Parser documentParser)
: base(dict)
{
int n = Elements.GetInteger(Keys.N);
int first = Elements.GetInteger(Keys.First);
- Stream.TryUncompress();
+ Stream?.TryUncompress();
+
+ if (Stream == null)
+ throw new InvalidOperationException("PdfObjectStream has no stream. Unexpected error.");
- var parser = new Parser(_document, new MemoryStream(Stream.UnfilteredValue), documentParser);
+ var parser = new Parser(base.Document, new MemoryStream(Stream.UnfilteredValue), documentParser);
_header = parser.ReadObjectStreamHeader(n, first);
#if DEBUG_ && CORE
@@ -49,55 +63,36 @@ internal PdfObjectStream(PdfDictionary dict, Parser documentParser)
}
///
- /// Reads all references inside the ObjectStream and returns all ObjectIDs and offsets for its objects.
+ /// Returns all ObjectIDs and read positions for the objects inside this ObjectStream.
///
- internal ICollection> ReadReferencesAndOffsets(PdfCrossReferenceTable xrefTable)
+ internal ICollection> ReadObjectIDsWithOffsets()
{
var length = _header.Length;
- _objectOffsets = [];
- ////// Create parser for stream.
- ////Parser parser = new Parser(_document, new MemoryStream(Stream.Value));
- for (var idx = 0; idx < length; idx++)
+ Dictionary objectOffsets = [];
+
+ // For duplicate IDs the newest object should be read first, to ignore older objects with the same ID read later.
+ // Therefore, we read the offsets from high to low.
+ for (var idx = length - 1; idx >= 0; idx--)
{
int objectNumber = _header[idx][0];
int offset = _header[idx][1];
var objectID = new PdfObjectID(objectNumber);
- // -1 indicates compressed object.
- var iref = PdfReference.CreateForObjectID(objectID, -1);
-
- _objectOffsets.Add(objectID, offset);
-
- if (!xrefTable.Contains(iref.ObjectID))
+ // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd
+ if (!objectOffsets.ContainsKey(objectID))
{
- xrefTable.Add(iref);
+ objectOffsets.Add(objectID, offset);
}
else
{
-#if DEBUG_
- _ = typeof(int);
-#endif
+ // Ignore object with objectID already on the list.
+ PdfSharpLogHost.PdfReadingLogger.LogWarning("Ignoring object with ID {objectID} while reading offsets in object stream {objectStreamId} because an object with that ID was already read in that object stream.", objectID, ObjectID);
}
}
- return _objectOffsets;
- }
-
- ///
- /// Tries to get the position of the PdfObject inside this ObjectStream.
- ///
- internal bool TryGetObjectOffset(PdfObjectID pdfObjectID, out SizeType offset, SuppressExceptions? suppressObjectOrderExceptions)
- {
- if (_objectOffsets == null)
- {
- SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.InvalidOperationException_ReferencesOfObjectStreamNotYetRead());
- offset = -1;
- return false;
- }
-
- return _objectOffsets.TryGetValue(pdfObjectID, out offset);
+ return objectOffsets;
}
///
@@ -108,11 +103,6 @@ internal bool TryGetObjectOffset(PdfObjectID pdfObjectID, out SizeType offset, S
///
readonly int[][] _header = default!; // Reference: Page 102
- ///
- /// Manages the read positions for all PdfObjects inside this ObjectStream.
- ///
- Dictionary? _objectOffsets;
-
///
/// Predefined keys common to all font dictionaries.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPageInheritableObjects.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPageInheritableObjects.cs
index 48c88455..ae4bae66 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPageInheritableObjects.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPageInheritableObjects.cs
@@ -5,6 +5,7 @@
namespace PdfSharp.Pdf.Advanced
{
+#if true_
///
/// Represents a PDF page object.
///
@@ -23,14 +24,14 @@ public PdfRectangle MediaBox
get => _mediaBox;
set => _mediaBox = value;
}
- PdfRectangle _mediaBox = default!;
+ PdfRectangle _mediaBox = null!;
public PdfRectangle CropBox
{
get => _cropBox;
set => _cropBox = value;
}
- PdfRectangle _cropBox = default!;
+ PdfRectangle _cropBox = null!;
public int Rotate
{
@@ -44,4 +45,5 @@ public int Rotate
}
int _rotate;
}
+#endif
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPlaceholder.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPlaceholder.cs
new file mode 100644
index 00000000..9130bd33
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPlaceholder.cs
@@ -0,0 +1,96 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Internal;
+using PdfSharp.Pdf.IO;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Advanced
+{
+ ///
+ /// Represents text that is written ‘as it is’ into the PDF stream.
+ /// Using this class can lead to invalid PDF files.
+ /// E.g. strings in a literal are not encrypted when the document is saved with a password.
+ ///
+ sealed class PdfPlaceholder(int length, char chPlaceholder = '?') : PdfPrimitive
+ {
+ public void SetValue(string value)
+ {
+ EnsureLength(value.Length);
+ Value = value;
+ _isEffectiveValueSet = true;
+ }
+
+ public void SetValue(byte[] bytes)
+ {
+ EnsureLength(bytes.Length);
+ Value = PdfEncoders.RawEncoding.GetString(bytes);
+ _isEffectiveValueSet = true;
+ }
+
+ ///
+ /// Gets the number of bytes of the signature.
+ ///
+ public int Length { get; init; } = length;
+
+ ///
+ /// Gets the value as literal string.
+ ///
+ public string Value { get; private set; } = new(chPlaceholder, length);
+
+ ///
+ /// Returns the current value.
+ ///
+ public override string ToString() => Value;
+
+ ///
+ /// Writes the placeholder item.
+ ///
+ internal override void WriteObject(PdfWriter writer)
+ => writer.Write(this, out _startPosition, out _endPosition);
+
+ ///
+ /// Writes the effective value at the placeholder position.
+ ///
+ public void WriteEffectiveValue(PdfWriter writer)
+ {
+ if (!_isEffectiveValueSet)
+ throw new InvalidOperationException("PdfPlaceholder cannot write the effective value because it is not set.");
+
+ // Save current writer position.
+ var oldPosition = writer.Position;
+
+ // Write Value at StartPosition.
+ Debug.Assert(Value.Length == Length);
+ writer.Stream.Position = StartPosition;
+ writer.WriteRaw(Value);
+
+ // Restore old writer position.
+ writer.Stream.Position = oldPosition;
+ }
+
+ ///
+ /// Position of the first byte of this item in PdfWriter’s stream.
+ ///
+ public SizeType StartPosition => _startPosition;
+ SizeType _startPosition;
+
+ ///
+ /// Position of the last byte of this item in PdfWriter’s stream.
+ ///
+ public SizeType EndPosition => _endPosition;
+ SizeType _endPosition;
+
+ bool _isEffectiveValueSet;
+
+ void EnsureLength(int length)
+ {
+ if (Length != length)
+ {
+ throw new InvalidOperationException(
+ Invariant($"The length of the item must be '{Length}', but it is '{length}'."));
+ }
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs
index 7240a8b5..e4327e93 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs
@@ -2,12 +2,15 @@
// See the LICENSE file in the solution root for more information.
// With this define each iref object gets a unique number (uid) to make them distinguishable in the debugger.
-#define UNIQUE_IREF_
+#define UNIQUE_IREF_xxx // TODO Move to Directory.Build.targets
using Microsoft.Extensions.Logging;
using PdfSharp.Logging;
using PdfSharp.Pdf.IO;
+// v7.0.0 TODO review MakeDocument and proxy objects
+
+
namespace PdfSharp.Pdf.Advanced
{
///
@@ -16,30 +19,15 @@ namespace PdfSharp.Pdf.Advanced
[DebuggerDisplay("iref({ObjectNumber}, {GenerationNumber})")]
public sealed class PdfReference : PdfItem
{
- // About PdfReference
- //
- // * A PdfReference holds either the ObjectID or the PdfObject or both.
- //
- // * Each PdfObject has a PdfReference if and only if it is an indirect object. Direct objects have
- // no PdfReference, because they are embedded in a parent object.
- //
- // * PdfReference objects are used to reference PdfObject instances. A value in a PDF dictionary
- // or array that is a PdfReference represents an indirect reference. A value in a PDF dictionary
- // or array that is a PdfObject represents a direct (or embedded) object.
- //
- // * When a PDF file is imported, the PdfXRefTable is filled with PdfReference objects keeping the
- // ObjectsIDs and file positions (offsets) of all indirect objects.
- //
- // * Indirect objects can easily be renumbered because they do not rely on their ObjectsIDs.
- //
- // * During modification of a document the ObjectID of an indirect object has no meaning,
- // except that they must be different in pairs.
-
///
/// Initializes a new PdfReference instance for the specified indirect object.
/// An indirect PDF object has one and only one reference.
/// You cannot create an instance of PdfReference.
///
+ /// A direct PDF object.
+ /// The object ID. Can be undefined.
+ /// The position in the PDF file stream, or -1, if unknown.
+ ///
PdfReference(PdfObject pdfObject, PdfObjectID objectID, SizeType position)
{
if (pdfObject.Reference != null)
@@ -50,19 +38,14 @@ public sealed class PdfReference : PdfItem
if (position != 0)
_position = position;
- //else
- //{
- // // Can be 0 or -1.
- // //_ = typeof(int);
- // //PdfSharpLogHost.DocumentProcessingLogger.LogError("Position is 0.");
- //}
#if UNIQUE_IREF && DEBUG
_uid = ++s_counter;
#endif
}
///
- /// Initializes a new PdfReference instance from the specified object identifier and file position.
+ /// Initializes a new instance of the class from the specified
+ /// object identifier and file position.
///
PdfReference(PdfObjectID objectID, SizeType position)
{
@@ -73,15 +56,45 @@ public sealed class PdfReference : PdfItem
#endif
}
+ ///
+ /// Initializes a new instance of the class for a proxy container.
+ /// Used for objects that represents two different types, e.g. a PDF dictionary that is both
+ /// an interactive field and a widget annotation.
+ ///
+ /// The dominant container which is in the IRefTable.
+ /// The proxy container which will use the Elements of the master.
+ internal PdfReference(PdfContainer cont, PdfContainer proxy)
+ : base(cont)
+ {
+ Debug.Assert(cont.IsIndirect, "The dominant container must be an indirect object.");
+ Debug.Assert(!proxy.IsIndirect, "The proxy container must be a direct object.");
+
+ // DELETE ItemFlags = ItemFlags.IsProxyReference;
+
+ var xref = cont.RequiredReference;
+ _document = xref.Document;
+ _objectID = xref.ObjectID;
+ _position = xref.Position;
+ _value = proxy;
+
+ // Make proxy an indirect object that only exists in memory.
+ proxy.Reference = this;
+ }
+
///
/// Creates a PdfReference from a PdfObject.
///
- ///
- ///
- ///
+ /// A direct PDF object.
+ /// The object ID. Can be undefined.
+ /// The position in the PDF file stream, or -1, if unknown.
///
internal static PdfReference CreateFromObject(PdfObject pdfObject, PdfObjectID objectID, SizeType position)
- => new(pdfObject, objectID, position);
+ {
+#if TEST_CODE
+ Debug.Assert(pdfObject.ObjectID.IsEmpty || pdfObject.ObjectID == objectID); // TODO Remove objectID in parameters?
+#endif
+ return new(pdfObject, objectID, position);
+ }
///
/// Creates a PdfReference from a PdfObjectID.
@@ -90,7 +103,9 @@ internal static PdfReference CreateFromObject(PdfObject pdfObject, PdfObjectID o
///
///
internal static PdfReference CreateForObjectID(PdfObjectID objectID, SizeType position)
- => new(objectID, position);
+ {
+ return new(objectID, position);
+ }
///
/// Writes the object in PDF iref table format.
@@ -99,7 +114,7 @@ internal void WriteXRefEntry(PdfWriter writer)
{
// PDFsharp does not yet support PDF 1.5 object streams for writing.
- // Each line must be exactly 20 bytes long, otherwise Acrobat repairs the file.
+ // Each line must be exactly 20 bytes long, otherwise Acrobat wants to repair the file.
writer.WriteRaw(Invariant($"{_position:0000000000} {_objectID.GenerationNumber:00000} n \n"));
}
@@ -118,22 +133,8 @@ internal override void WriteObject(PdfWriter writer)
public PdfObjectID ObjectID
{
get => _objectID;
- set
- {
- _objectID = value;
-#if true_
- if (Document != null)
- {
- //PdfXRefTable table = Document.xrefTable;
- //table.Remove(this);
- //objectID = value;
- //table.Add(this);
- }
-#endif
- }
+ internal set => _objectID = value;
}
-
- // ReSharper disable once InconsistentNaming
PdfObjectID _objectID;
///
@@ -163,6 +164,9 @@ internal set
///
/// Gets or sets the referenced PdfObject.
///
+ ///
+ /// The returned value can be null during the reading of a PDF file.
+ ///
public PdfObject Value
{
get => _value;
@@ -170,8 +174,19 @@ internal set
{
Debug.Assert(value != null, "The value of a PdfReference must never be null.");
Debug.Assert(value.Reference == null || ReferenceEquals(value.Reference, this), "The reference of the value must be null or this.");
- _value = value;
+
// value must never be null.
+ if (value == null!)
+ throw new ArgumentNullException(nameof(value), "value must not be null.");
+
+ _value = value;
+#if DEBUG
+ if (value.Reference != null)
+ {
+ _ = typeof(int);
+ Debug.Assert(ReferenceEquals(value.Reference, this));
+ }
+#endif
value.Reference = this;
}
}
@@ -183,7 +198,7 @@ internal set
internal void ResetObject() => _value = null!;
///
- /// Gets or sets the document this object belongs to.
+ /// Gets the document this object belongs to.
///
public PdfDocument Document
{
@@ -191,13 +206,11 @@ public PdfDocument Document
{
#if DEBUG
if (_document == null)
- {
PdfSharpLogHost.Logger.LogDebug("Document of object {_objectID} is null.", _objectID);
- }
#endif
return _document!;
}
- set => _document = value;
+ internal set => _document = value;
}
PdfDocument? _document;
@@ -206,24 +219,58 @@ public PdfDocument Document
///
public override string ToString() => _objectID + " R";
+ ///
+ /// Returns true if the specified item is an indirect object; false otherwise.
+ ///
+ public static bool IsIndirect(PdfItem item) // PDFsharp/NT
+ => item is PdfReference or PdfObject { Reference: not null };
+
///
/// Dereferences the specified item. If the item is a PdfReference, the item is set
/// to the referenced value. Otherwise, no action is taken.
///
- public static void Dereference(ref object item)
+ public static void Dereference(ref PdfItem item) // Do not change to 'ref PdfItem?'.
{
if (item is PdfReference reference)
item = reference.Value;
}
///
- /// Dereferences the specified item. If the item is a PdfReference, the item is set
- /// to the referenced value. Otherwise, no action is taken.
+ /// If the specified item is an indirect object it is replaced by its PdfReference.
+ /// Otherwise, no action is taken.
///
- public static void Dereference(ref PdfItem item)
+ internal static void ToReference(ref PdfItem item)
{
- if (item is PdfReference reference)
- item = reference.Value;
+ if (item is PdfObject { Reference: not null } obj)
+ item = obj.RequiredReference;
+ }
+
+ ///
+ /// Makes a PDF object an indirect object of the specified document
+ /// and returns a reference to it.
+ ///
+ /// A newly created object.
+ /// The PDF document the object is added as indirect object.
+ public static PdfReference MakeIndirect(PdfObject obj, PdfDocument doc) // PDFsharp/NT
+ {
+ if (obj == null)
+ throw new ArgumentNullException(nameof(obj));
+ //ArgumentNullException.ThrowIfNull(obj);
+ //ArgumentNullExceptionEx.ThrowIfNull(obj);
+
+ if (doc == null)
+ throw new ArgumentNullException(nameof(doc));
+
+ if (obj.IsIndirect)
+ throw new InvalidOperationException("Object is already an indirect object.");
+
+ if (obj.Owner != null! && obj.Owner != doc)
+ throw new InvalidOperationException("Object already has an owner that is not the specified document.");
+
+ doc.Internals.AddObject(obj);
+
+ Debug.Assert(obj.Reference != null);
+ return obj.Reference;
}
internal static PdfReferenceComparer Comparer => new();
@@ -238,13 +285,46 @@ public int Compare(PdfReference? l, PdfReference? r)
if (l != null)
{
if (r != null)
- return l._objectID.CompareTo(r._objectID);
+ {
+ if (l.ObjectNumber == r.ObjectNumber)
+ return l.GenerationNumber - r.GenerationNumber;
+ return l.ObjectNumber - r.ObjectNumber;
+ }
return -1;
}
return r != null ? 1 : 0;
}
}
+ internal void SetTemp() => ItemFlags |= ItemFlags.IsTempRef;
+
+ internal void ClearTemp() => ItemFlags &= ~ItemFlags.IsTempRef;
+
+ internal bool IsTemp() => (ItemFlags & ItemFlags.IsTempRef) != 0;
+
+ ///
+ /// Not yet used.
+ ///
+ internal int AddRef() // TODO: Use it for all indirect objects.
+ {
+ return Interlocked.Increment(ref _refCount);
+ }
+
+ ///
+ /// Not yet used.
+ ///
+ internal int Release()
+ {
+ return Interlocked.Decrement(ref _refCount);
+ }
+
+ ///
+ /// Gets the reference count of this PdfReference.
+ ///
+ internal int RefCounter => _refCount;
+
+ int _refCount;
+
#if UNIQUE_IREF && DEBUG
static int s_counter = 0;
int _uid;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResourceMap.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResourceMap.cs
index ccaebd82..61703184 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResourceMap.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResourceMap.cs
@@ -1,14 +1,14 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System.Collections.Generic;
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Advanced
{
///
/// Base class for all dictionaries that map resource names to objects.
///
- class PdfResourceMap : PdfDictionary
+ public class PdfResourceMap : PdfDictionary
{
public PdfResourceMap()
{ }
@@ -21,16 +21,16 @@ protected PdfResourceMap(PdfDictionary dict)
: base(dict)
{ }
- // public int Count
- // {
- // get {return resources.Count;}
- // }
+ // public int Count
+ // {
+ // get {return resources.Count;}
+ // }
//
- // public PdfObject this[string key]
- // {
- // get {return resources[key] as PdfObject;}
- // set {resources[key] = value;}
- // }
+ // public PdfObject this[string key]
+ // {
+ // get {return resources[key] as PdfObject;}
+ // set {resources[key] = value;}
+ // }
///
/// Adds all imported resource names to the specified hashtable.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResources.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResources.cs
index c2a077a7..5f61b66f 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResources.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResources.cs
@@ -22,9 +22,20 @@ public sealed class PdfResources : PdfDictionary
public PdfResources(PdfDocument document)
: base(document)
{
- Elements[Keys.ProcSet] = new PdfLiteral("[/PDF/Text/ImageB/ImageC/ImageI]");
+ //Elements[Keys.ProcSet] = new PdfLiteral("[/PDF/Text/ImageB/ImageC/ImageI]");
+ // #US373 begin Must be a PdfArray.
+ Elements[Keys.ProcSet] = new PdfArray(new PdfName("/PDF"),
+ new PdfName("/Text"),
+ new PdfName("/ImageB"),
+ new PdfName("/ImageC"),
+ new PdfName("/ImageI"));
+ // #US373 end
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfResources(PdfDictionary dict)
: base(dict)
{ }
@@ -40,7 +51,7 @@ public string AddFont(PdfFont font)
_resources[font] = name;
if (font.Reference == null)
Owner.IrefTable.Add(font);
- Fonts.Elements[name] = font.Reference;
+ Fonts.Elements[name] = font.RequiredReference;
}
return name;
}
@@ -57,7 +68,7 @@ public string AddImage(PdfImage image)
_resources[image] = name;
if (image.Reference == null)
Owner.IrefTable.Add(image);
- XObjects.Elements[name] = image.Reference;
+ XObjects.Elements[name] = image.RequiredReference;
}
return name;
}
@@ -74,7 +85,7 @@ public string AddForm(PdfFormXObject form)
_resources[form] = name;
if (form.Reference == null)
Owner.IrefTable.Add(form);
- XObjects.Elements[name] = form.Reference;
+ XObjects.Elements[name] = form.RequiredReference;
}
return name;
}
@@ -91,7 +102,7 @@ public string AddExtGState(PdfExtGState extGState)
_resources[extGState] = name;
if (extGState.Reference == null)
Owner.IrefTable.Add(extGState);
- ExtGStates.Elements[name] = extGState.Reference;
+ ExtGStates.Elements[name] = extGState.RequiredReference;
}
return name;
}
@@ -108,7 +119,7 @@ public string AddPattern(PdfShadingPattern pattern)
_resources[pattern] = name;
if (pattern.Reference == null)
Owner.IrefTable.Add(pattern);
- Patterns.Elements[name] = pattern.Reference;
+ Patterns.Elements[name] = pattern.RequiredReference;
}
return name;
}
@@ -125,7 +136,7 @@ public string AddPattern(PdfTilingPattern pattern)
_resources[pattern] = name;
if (pattern.Reference == null)
Owner.IrefTable.Add(pattern);
- Patterns.Elements[name] = pattern.Reference;
+ Patterns.Elements[name] = pattern.RequiredReference;
}
return name;
}
@@ -142,7 +153,7 @@ public string AddShading(PdfShading shading)
_resources[shading] = name;
if (shading.Reference == null)
Owner.IrefTable.Add(shading);
- Shadings.Elements[name] = shading.Reference;
+ Shadings.Elements[name] = shading.RequiredReference;
}
return name;
}
@@ -293,25 +304,46 @@ internal bool ExistsResourceName(string name)
{
_importedResourceNames = new();
- if (Elements[Keys.Font] != null)
+ //if (Elements[Keys.Font] != null) // TODO #US373 Just a null check.
+ // Fonts.CollectResourceNames(_importedResourceNames);
+
+ //if (Elements[Keys.XObject] != null) // TODO #US373 Just a null check.
+ // XObjects.CollectResourceNames(_importedResourceNames);
+
+ //if (Elements[Keys.ExtGState] != null) // TODO #US373 Just a null check.
+ // ExtGStates.CollectResourceNames(_importedResourceNames);
+
+ //if (Elements[Keys.ColorSpace] != null) // TODO #US373 Just a null check.
+ // ColorSpaces.CollectResourceNames(_importedResourceNames);
+
+ //if (Elements[Keys.Pattern] != null) // TODO #US373 Just a null check.
+ // Patterns.CollectResourceNames(_importedResourceNames);
+
+ //if (Elements[Keys.Shading] != null) // TODO #US373 Just a null check.
+ // Shadings.CollectResourceNames(_importedResourceNames);
+
+ //if (Elements[Keys.Properties] != null) // TODO #US373 Just a null check.
+ // Properties.CollectResourceNames(_importedResourceNames);
+
+ if (Elements.HasValue(Keys.Font)) // #US373
Fonts.CollectResourceNames(_importedResourceNames);
- if (Elements[Keys.XObject] != null)
+ if (Elements.HasValue(Keys.XObject)) // #US373
XObjects.CollectResourceNames(_importedResourceNames);
- if (Elements[Keys.ExtGState] != null)
+ if (Elements.HasValue(Keys.ExtGState)) // #US373
ExtGStates.CollectResourceNames(_importedResourceNames);
- if (Elements[Keys.ColorSpace] != null)
+ if (Elements.HasValue(Keys.ColorSpace)) // #US373
ColorSpaces.CollectResourceNames(_importedResourceNames);
- if (Elements[Keys.Pattern] != null)
+ if (Elements.HasValue(Keys.Pattern)) // #US373
Patterns.CollectResourceNames(_importedResourceNames);
- if (Elements[Keys.Shading] != null)
+ if (Elements.HasValue(Keys.Shading)) // #US373
Shadings.CollectResourceNames(_importedResourceNames);
- if (Elements[Keys.Properties] != null)
+ if (Elements.HasValue(Keys.Properties)) // #US373
Properties.CollectResourceNames(_importedResourceNames);
}
return _importedResourceNames.ContainsKey(name);
@@ -390,7 +422,6 @@ public sealed class Keys : KeysBase
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShading.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShading.cs
index 496597c6..94a951dc 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShading.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShading.cs
@@ -26,6 +26,14 @@ public PdfShading(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfShading(PdfDictionary dict)
+ : base(dict)
+ { }
+
///
/// Setups the shading from the specified brush.
///
@@ -34,7 +42,7 @@ internal void SetupFromBrush(XLinearGradientBrush brush, XGraphicsPdfRenderer re
if (brush == null)
throw new ArgumentNullException(nameof(brush));
- PdfColorMode colorMode = _document.Options.ColorMode;
+ PdfColorMode colorMode = Document.Options.ColorMode;
XColor color1 = ColorSpaceHelper.EnsureColorMode(colorMode, brush._color1);
XColor color2 = ColorSpaceHelper.EnsureColorMode(colorMode, brush._color2);
@@ -115,7 +123,7 @@ internal void SetupFromBrush(XLinearGradientBrush brush, XGraphicsPdfRenderer re
///
/// Common keys for all streams.
///
- internal sealed class Keys : KeysBase
+ public sealed class Keys : KeysBase
{
///
/// (Required) The shading type:
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShadingPattern.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShadingPattern.cs
index f45b2264..dd2f85bc 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShadingPattern.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShadingPattern.cs
@@ -29,6 +29,17 @@ public PdfShadingPattern(PdfDocument document)
Elements[Keys.PatternType] = new PdfInteger(2);
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfShadingPattern(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.Type, "/Pattern");
+ Elements[Keys.PatternType] = new PdfInteger(2);
+ }
+
///
/// Setups the shading pattern from the specified brush.
///
@@ -37,7 +48,8 @@ internal void SetupFromBrush(XLinearGradientBrush brush, XMatrix matrix, XGraphi
if (brush == null)
throw new ArgumentNullException(nameof(brush));
- PdfShading shading = new PdfShading(_document);
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ PdfShading shading = new PdfShading(Document);
shading.SetupFromBrush(brush, renderer);
Elements[Keys.Shading] = shading;
//Elements[Keys.Matrix] = new PdfLiteral("[" + PdfEncoders.ToString(matrix) + "]");
@@ -89,7 +101,6 @@ internal void SetupFromBrush(XLinearGradientBrush brush, XMatrix matrix, XGraphi
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs
index bd5dbe95..913457af 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs
@@ -26,6 +26,16 @@ public PdfSoftMask(PdfDocument document)
Elements.SetName(Keys.Type, "/Mask");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfSoftMask(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.Type, "/Mask");
+ }
+
///
/// Predefined keys of this dictionary.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTilingPattern.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTilingPattern.cs
index 2af54d94..6d3dc26b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTilingPattern.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTilingPattern.cs
@@ -26,6 +26,17 @@ public PdfTilingPattern(PdfDocument document)
Elements[Keys.PatternType] = new PdfInteger(1);
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfTilingPattern(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.Type, "/Pattern");
+ Elements[Keys.PatternType] = new PdfInteger(1);
+ }
+
/////
///// Setups the shading pattern from the specified brush.
/////
@@ -134,7 +145,6 @@ public PdfTilingPattern(PdfDocument document)
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs
index 928b384a..bbe2f0f6 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs
@@ -7,27 +7,39 @@
using PdfSharp.Logging;
using PdfSharp.Pdf.Filters;
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
namespace PdfSharp.Pdf.Advanced
{
///
/// Represents a ToUnicode map for composite font.
///
- sealed class PdfToUnicodeMap : PdfDictionary
+ public sealed class PdfToUnicodeMap : PdfDictionary
{
- public PdfToUnicodeMap(PdfDocument document)
- : base(document)
- { }
+ //public PdfToUnicodeMap(PdfDocument document)
+ // : base(document)
+ //{ }
- public PdfToUnicodeMap(PdfDocument document, CMapInfo cmapInfo)
+ internal PdfToUnicodeMap(PdfDocument document, CMapInfo cmapInfo)
: base(document)
{
CMapInfo = cmapInfo;
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfToUnicodeMap(PdfDictionary dict)
+ : base(dict)
+ {
+ CMapInfo = null!;
+ }
+
///
/// Gets or sets the CMap info.
///
- public CMapInfo CMapInfo { get; set; } = default!;
+ internal CMapInfo CMapInfo { get; set; }
///
/// Creates the ToUnicode map from the CMapInfo.
@@ -41,7 +53,7 @@ internal override void PrepareForSave()
"/CIDInit /ProcSet findresource begin\n" +
"12 dict begin\n" +
"begincmap\n" +
- "/CIDSystemInfo << /Registry (Adobe)/Ordering (UCS)/Supplement 0>> def\n" +
+ "/CIDSystemInfo <> def\n" +
"/CMapName /Adobe-Identity-UCS def /CMapType 2 def\n";
string suffix = "endcmap CMapName currentdict /CMap defineresource pop end end";
@@ -98,12 +110,13 @@ internal override void PrepareForSave()
if (Owner.Options.CompressContentStreams)
{
- Elements.SetName("/Filter", "/FlateDecode");
- bytes = Filtering.FlateDecode.Encode(bytes, _document.Options.FlateEncodeMode);
+ Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ bytes = Filtering.FlateDecode.Encode(bytes, Document.Options.FlateEncodeMode);
}
else
{
- Elements.Remove("/Filter");
+ Elements.Remove(PdfStream.Keys.Filter);
}
if (Stream == null!)
@@ -111,7 +124,7 @@ internal override void PrepareForSave()
else
{
Stream.Value = bytes;
- Elements.SetInteger("/Length", Stream.Length);
+ Elements.SetInteger(PdfStream.Keys.Length, Stream.Length);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrailer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrailer.cs
index 1c57bada..5bb06fa8 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrailer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrailer.cs
@@ -1,35 +1,54 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal;
+using PdfSharp.Pdf.Internal;
using PdfSharp.Pdf.IO;
using PdfSharp.Pdf.Security;
-using PdfSharp.Pdf.Internal;
+
+// v7.0.0 Ready
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Advanced
{
///
- /// Represents a PDF trailer dictionary. Even though trailers are dictionaries they never have a cross
- /// reference entry in PdfReferenceTable.
+ /// Represents a PDF trailer dictionary. Even though trailers are dictionaries they never have a
+ /// cross-reference entry in PdfReferenceTable.
///
// Reference: 3.4.4 File Trailer / Page 96
- class PdfTrailer : PdfDictionary
+ public class PdfTrailer : PdfDictionary
{
+ // Reference 2.0: 7.5.5 File trailer / Page 58
+ // - and -
+ // Reference 2.0: 12.7.8.2.4 FDF trailer / Page 558
+
///
- /// Initializes a new instance of PdfTrailer.
+ /// Initializes a new instance of the class from a PdfDocument.
///
public PdfTrailer(PdfDocument document)
: base(document)
{
- _document = document;
+ Debug.Assert(ReferenceEquals(_document2, Document));
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfTrailer(PdfDictionary dict)
+ : base(dict)
+ { }
+
///
/// Initializes a new instance of the class from a .
///
public PdfTrailer(PdfCrossReferenceStream trailer)
- : base(trailer._document)
+ : base(trailer.Document)
{
- _document = trailer._document;
+ // ReSharper disable once VirtualMemberCallInConstructor
+ Debug.Assert(ReferenceEquals(_document2, trailer.Document));
+ Debug.Assert(ReferenceEquals(Document, trailer._document2));
SecurityHandlerInternal = trailer.SecurityHandlerInternal;
// /ID [<09F877EBF282E9408ED1882A9A21D9F2><2A4938E896006F499AC1C2EA7BFB08E4>]
@@ -47,7 +66,11 @@ public PdfTrailer(PdfCrossReferenceStream trailer)
var id = trailer.Elements.GetArray(Keys.ID);
if (id != null)
+ {
+ // TODO: Check what happened here: Why found we this bug so late?
+ id = id.Clone();
Elements.SetValue(Keys.ID, id);
+ }
}
public int Size
@@ -56,35 +79,35 @@ public int Size
set => Elements.SetInteger(Keys.Size, value);
}
- //public int Prev needed when linearized..
- //{
- // get {return Elements.GetInteger(Keys.Prev);}
- //}
-
- public PdfDocumentInformation Info => (PdfDocumentInformation)Elements.GetValue(Keys.Info, VCF.CreateIndirect)!; // Because of CreateIndirect.
+ public PdfDocumentInformation Info => Elements.GetRequiredValue(Keys.Info, VCF.CreateIndirect);
///
/// (Required; must be an indirect reference)
/// The catalog dictionary for the PDF document contained in the file.
///
- public PdfCatalog Root => (PdfCatalog)Elements.GetValue(PdfTrailer.Keys.Root, VCF.CreateIndirect)!; // Because of CreateIndirect.
+ public PdfCatalog Root => Elements.GetRequiredValue(PdfTrailer.Keys.Root, VCF.CreateIndirect);
///
/// Gets the first or second document identifier.
+ /// The index must be 0 or 1.
///
public string GetDocumentID(int index)
{
if (index is < 0 or > 1)
throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be 0 or 1.");
- var array = Elements[Keys.ID] as PdfArray;
- if (array == null || array.Elements.Count < 2)
+ //var array = Elements[Keys.ID] as PdfArray;
+ //if (array == null || array.Elements.Count < 2)
+
+ //if (Elements[Keys.ID] is not PdfArray array || array.Elements.Count < 2)
+ if (Elements.GetArray(Keys.ID) is not { } array || array.Elements.Count < 2) // #US373
return "";
-#if true
- var item = array.Elements[index] as PdfString;
- if (item != null)
+
+ //var item = array.Elements[index] as PdfString;
+ //if (item != null)
+ if (array.Elements[index] is PdfString item)
{
- // The DocumentID is just a hex string, never represents unicode content.
+ // The DocumentID is just a hex string, never represents Unicode content.
// Add FEFF if it was truncated from the ID. Unlikely, but can happen.
if ((item.Flags & PdfStringFlags.Unicode) != 0)
{
@@ -93,11 +116,6 @@ public string GetDocumentID(int index)
}
return item.Value;
}
-#else
- var item = array.Elements[index];
- if (item is PdfString pdfString)
- return pdfString.Value;
-#endif
return "";
}
@@ -109,8 +127,11 @@ public void SetDocumentID(int index, string value)
if (index is < 0 or > 1)
throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be 0 or 1.");
- var array = Elements[Keys.ID] as PdfArray;
- if (array == null || array.Elements.Count < 2)
+ //var array = Elements[Keys.ID] as PdfArray;
+ //if (array == null || array.Elements.Count < 2)
+
+ //if (Elements[Keys.ID] is not PdfArray array || array.Elements.Count < 2)
+ if (Elements.GetArray(Keys.ID) is not { } array || array.Elements.Count < 2) // #US373
array = CreateNewDocumentIDs();
array.Elements[index] = new PdfString(value, PdfStringFlags.HexLiteral);
}
@@ -120,11 +141,13 @@ public void SetDocumentID(int index, string value)
///
internal PdfArray CreateNewDocumentIDs()
{
- var array = new PdfArray(_document);
+ Debug.Assert(ReferenceEquals(_document2, Document));
+ var array = new PdfArray(Document);
byte[] docID = Guid.NewGuid().ToByteArray();
string id = PdfEncoders.RawEncoding.GetString(docID, 0, docID.Length);
- array.Elements.Add(new PdfString(id, PdfStringFlags.HexLiteral));
- array.Elements.Add(new PdfString(id, PdfStringFlags.HexLiteral));
+ var str = new PdfString(id, PdfStringFlags.HexLiteral);
+ array.Elements.Add(str);
+ array.Elements.Add(str);
Elements[Keys.ID] = array;
return array;
}
@@ -153,13 +176,14 @@ public PdfStandardSecurityHandler SecurityHandler
internal override void WriteObject(PdfWriter writer)
{
// Delete /XRefStm entry, if any.
- // HACK_OLD:
- _elements?.Remove(Keys.XRefStm);
+ // HACK_OLD: TODO When can this happen??
+ Elements.Remove(Keys.XRefStm);
// Don’t encrypt myself.
var effectiveSecurityHandler = writer.EffectiveSecurityHandler;
writer.EffectiveSecurityHandler = null;
base.WriteObject(writer);
+ writer.NewLine();
writer.EffectiveSecurityHandler = effectiveSecurityHandler;
}
@@ -169,79 +193,92 @@ internal override void WriteObject(PdfWriter writer)
internal void Finish()
{
PdfReference? iref;
+
// /Root
- var currentTrailer = _document.Trailer;
+ var currentTrailer = Document.Trailer;
+ Debug.Assert(ReferenceEquals(_document2, Document));
do
{
- iref = currentTrailer.Elements[Keys.Root] as PdfReference;
+ //iref = currentTrailer.Elements[Keys.Root] as PdfReference;
+ iref = currentTrailer.Elements.GetReference(Keys.Root); // TODO #US373
//if (iref != null && iref.Value == null)
if (iref is { Value: null })
{
- iref = _document.IrefTable[iref.ObjectID];
+ iref = Document.IrefTable[iref.ObjectID];
Debug.Assert(iref is not null && iref.Value != null);
- _document.Trailer.Elements[Keys.Root] = iref;
+ currentTrailer.Elements[Keys.Root] = iref;
}
currentTrailer = currentTrailer.PreviousTrailer;
} while (currentTrailer != null);
// /Info
- iref = _document.Trailer.Elements[Keys.Info] as PdfReference;
+ //iref = Document.Trailer.Elements[Keys.Info] as PdfReference;
+ iref = Document.Trailer.Elements.GetReference(Keys.Info); // TODO #US373
if (iref is { Value: null })
{
- iref = _document.IrefTable[iref.ObjectID];
- Debug.Assert(iref is not null && iref.Value != null);
- _document.Trailer.Elements[Keys.Info] = iref;
+ iref = Document.IrefTable[iref.ObjectID];
+ Debug.Assert(iref?.Value != null);
+ Document.Trailer.Elements[Keys.Info] = iref;
}
// /Encrypt
- iref = _document.Trailer.Elements[Keys.Encrypt] as PdfReference;
+ //iref = Document.Trailer.Elements[Keys.Encrypt] as PdfReference;
+ iref = Document.Trailer.Elements.GetReference(Keys.Encrypt); // TODO #US373
if (iref != null)
{
- iref = _document.IrefTable[iref.ObjectID];
- Debug.Assert(iref?.Value != null);
- _document.Trailer.Elements[Keys.Encrypt] = iref;
+ if (iref.Value == null!)
+ {
+ iref = Document.IrefTable[iref.ObjectID];
+ Debug.Assert(iref?.Value != null);
+ Document.Trailer.Elements[Keys.Encrypt] = iref;
+ }
// The encryption dictionary (security handler) was read in before the XRefTable construction
// was completed. The next lines fix that state (it took several hours to find these bugs...).
- var securityHandler = _document.Trailer.SecurityHandlerInternal!;
+ var securityHandler = Document.Trailer.SecurityHandlerInternal!;
securityHandler.Reference = null; // Reference will be updated new when setting iref.Value.
iref.Value = securityHandler;
}
Elements.Remove(Keys.Prev);
- Debug.Assert(_document.IrefTable.IsUnderConstruction == false);
- _document.IrefTable.IsUnderConstruction = false;
+ Debug.Assert(!Document.IrefTable.IsUnderConstruction);
+ Document.IrefTable.IsUnderConstruction = false;
}
///
/// Predefined keys of this dictionary.
///
- internal class Keys : KeysBase // Reference: TABLE 3.13 Entries in the file trailer dictionary / Page 97
+ public class Keys : KeysBase
{
+ // Reference 2.0: Table 15 — Entries in the file trailer dictionary / Page 58
+ // - and -
+ // Reference 2.0: Table 19 — Additional entries in a hybrid-reference file’s trailer dictionary / Page 58
+ // - and -
+ // Reference 2.0: Table 244 — Entry in the FDF trailer dictionary / Page 558
+
///
- /// (Required; must not be an indirect reference) The total number of entries in the file’s
- /// cross-reference table, as defined by the combination of the original section and all
- /// update sections. Equivalently, this value is 1 greater than the highest object number
- /// used in the file.
- /// Note: Any object in a cross-reference section whose number is greater than this value is
- /// ignored and considered missing.
+ /// (Required; shall not be an indirect reference) The total number of entries in the PDF
+ /// file’s cross-reference table, as defined by the combination of the original section
+ /// and all update sections. Equivalently, this value shall be 1 greater than the highest
+ /// object number defined in the PDF file.
+ /// Any object in a cross-reference section whose number is greater than this value shall
+ /// be ignored and defined to be missing by a PDF reader.
///
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string Size = "/Size";
///
- /// (Present only if the file has more than one cross-reference section; must not be an indirect
- /// reference) The byte offset from the beginning of the file to the beginning of the previous
- /// cross-reference section.
+ ///(Optional, present only if the file has more than one cross-reference section; shall
+ /// be a direct object) The byte offset from the beginning of the PDF file to the beginning
+ /// of the previous cross-reference stream.
///
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string Prev = "/Prev";
///
- /// (Required; must be an indirect reference) The catalog dictionary for the PDF document
- /// contained in the file.
+ /// (Required; shall be an indirect reference) The catalog dictionary object for this FDF file.
///
[KeyInfo(KeyType.Dictionary | KeyType.Required, typeof(PdfCatalog))]
public const string Root = "/Root";
@@ -253,22 +290,41 @@ internal class Keys : KeysBase // Reference: TABLE 3.13 Entries in the file tr
public const string Encrypt = "/Encrypt";
///
- /// (Optional; must be an indirect reference) The document’s information dictionary.
+ /// (Optional; shall be an indirect reference) The PDF file’s information dictionary.
+ /// As described in "Document information dictionary", this method for specifying document
+ /// metadata has been deprecated in PDF 2.0 and should therefore only be used to encode
+ /// information that is stated as required elsewhere in this document.
+ /// NOTE 1
+ /// The ModDate key within the Info dictionary is required if Page-Piece dictionaries(see 14.5, "Page-piece dictionaries") are used.
///
[KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(PdfDocumentInformation))]
public const string Info = "/Info";
///
- /// (Optional, but strongly recommended; PDF 1.1) An array of two strings constituting
- /// a file identifier for the file. Although this entry is optional,
- /// its absence might prevent the file from functioning in some workflows
- /// that depend on files being uniquely identified.
+ /// (Required in PDF 2.0 and later, or if an Encrypt entry is present; optional otherwise;
+ /// PDF 1.1) An array of two byte-strings constituting a PDF file identifier for the PDF file.
+ /// Each PDF file identifier byte-string shall have a minimum length of 16 bytes. If there
+ /// is an Encrypt entry, this array and the two byte-strings shall be direct objects and
+ /// shall be unencrypted.
+ /// NOTE 2
+ /// Because the ID entries are not encrypted, the ID key can be checked to assure that the
+ /// correct PDF file is being accessed without decrypting the PDF file. The restrictions
+ /// that the objects all be direct objects and not be encrypted ensure this.
+ /// NOTE 3
+ /// Although this entry is optional prior to PDF 2.0, its absence can prevent the PDF file
+ /// from functioning in some workflows that depend on PDF files being uniquely identified.
+ /// NOTE 4
+ /// The values of the ID strings are used as input to the encryption algorithm.If these
+ /// strings were indirect, or if the ID array were indirect, these strings would be encrypted
+ /// when written.This would result in a circular condition for a PDF reader: the ID strings
+ /// need be decrypted in order to use them to decrypt strings, including the ID strings
+ /// themselves.The preceding restriction prevents this circular condition.
///
[KeyInfo(KeyType.Array | KeyType.Optional)]
public const string ID = "/ID";
///
- /// (Optional) The byte offset from the beginning of the file of a cross-reference stream.
+ /// (Optional) The byte offset in the decoded stream from the beginning of the PDF file of a cross-reference stream.
///
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string XRefStm = "/XRefStm";
@@ -276,7 +332,7 @@ internal class Keys : KeysBase // Reference: TABLE 3.13 Entries in the file tr
///
/// Gets the KeysMeta for these keys.
///
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTransparencyGroupAttributes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTransparencyGroupAttributes.cs
index 3d0b403d..7249073c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTransparencyGroupAttributes.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTransparencyGroupAttributes.cs
@@ -14,6 +14,16 @@ internal PdfTransparencyGroupAttributes(PdfDocument thisDocument)
Elements.SetName(Keys.S, "/Transparency");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfTransparencyGroupAttributes(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.S, "/Transparency");
+ }
+
///
/// Predefined keys of this dictionary.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs
index 0969ea24..f6314292 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs
@@ -1,21 +1,32 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal.OpenType;
using PdfSharp.Fonts;
-using PdfSharp.Fonts.OpenType;
using PdfSharp.Drawing;
+using PdfSharp.Internal;
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Advanced
{
///
/// Represents a OpenType font that is ANSI encoded in the PDF document.
///
- class PdfTrueTypeFont : PdfFont
+ public class PdfTrueTypeFont : PdfFont
{
public PdfTrueTypeFont(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfTrueTypeFont(PdfDictionary dict)
+ : base(dict)
+ { }
+
///
/// Initializes a new instance of PdfTrueTypeFont from an XFont.
///
@@ -26,17 +37,16 @@ public PdfTrueTypeFont(PdfDocument document, XFont font)
Elements.SetName(Keys.Subtype, "/TrueType");
// TrueType with WinAnsiEncoding only.
- OpenTypeDescriptor otDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(font);
+ //var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache;
+ //OpenTypeDescriptor otDescriptor = (OpenTypeDescriptor)fontDescriptorCache.GetOrCreateDescriptorFor(font);
+ OpenTypeFontDescriptor otDescriptor = font.GlyphTypeface.OTFontFace.OTDescriptor;
+
FontDescriptor = document.PdfFontDescriptorCache.GetOrCreatePdfDescriptorFor(otDescriptor, font.GlyphTypeface.GetBaseName());
// When the font subset is created, the cmap table must be added.
FontDescriptor.AddCmapTable = true;
- //_fontOptions = font.PdfOptions;
- //Debug.Assert(_fontOptions != null);
-
- _cmapInfo = new CMapInfo(otDescriptor);
- //FontDescriptor._cmapInfo2 = new(otDescriptor);
+ CMapInfo = new CMapInfo(otDescriptor.FontFace.GlyphCount);
BaseFont = font.GlyphTypeface.GetBaseName();
// Fonts are always embedded.
@@ -48,7 +58,7 @@ public PdfTrueTypeFont(PdfDocument document, XFont font)
Encoding = "/WinAnsiEncoding";
Owner.IrefTable.TryAdd(FontDescriptor);
- Elements[Keys.FontDescriptor] = FontDescriptor.Reference;
+ Elements[Keys.FontDescriptor] = FontDescriptor.RequiredReference;
//FontEncoding = font.PdfOptions.FontEncoding;
FontEncoding = PdfFontEncoding.WinAnsi;
@@ -61,7 +71,9 @@ public PdfTrueTypeFont(PdfDocument document, XGlyphTypeface glyphTypeface)
Elements.SetName(Keys.Subtype, "/TrueType");
// TrueType with WinAnsiEncoding only.
- OpenTypeDescriptor otDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(glyphTypeface);
+ //var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache;
+ //OpenTypeDescriptor otDescriptor = (OpenTypeDescriptor)fontDescriptorCache.GetOrCreateDescriptorFor(glyphTypeface);
+ OpenTypeFontDescriptor otDescriptor = glyphTypeface.OTFontFace.OTDescriptor;
FontDescriptor = document.PdfFontDescriptorCache.GetOrCreatePdfDescriptorFor(otDescriptor, glyphTypeface.GetBaseName());
// When the font subset is created, the cmap table must be added.
@@ -70,7 +82,7 @@ public PdfTrueTypeFont(PdfDocument document, XGlyphTypeface glyphTypeface)
//_fontOptions = font.PdfOptions;
//Debug.Assert(_fontOptions != null);
- _cmapInfo = new CMapInfo(otDescriptor);
+ CMapInfo = new CMapInfo(otDescriptor.FontFace.GlyphCount);
//FontDescriptor._cmapInfo2 = new(otDescriptor);
// Fonts are always embedded.
@@ -82,7 +94,7 @@ public PdfTrueTypeFont(PdfDocument document, XGlyphTypeface glyphTypeface)
Encoding = "/WinAnsiEncoding";
Owner.IrefTable.TryAdd(FontDescriptor);
- Elements[Keys.FontDescriptor] = FontDescriptor.Reference;
+ Elements[Keys.FontDescriptor] = FontDescriptor.RequiredReference;
//FontEncoding = font.PdfOptions.FontEncoding;
FontEncoding = PdfFontEncoding.WinAnsi;
@@ -222,7 +234,8 @@ internal override void PrepareForSave()
/// Appendix D) or an encoding dictionary that specifies differences from the font’s
/// built-in encoding or from a specified predefined encoding.
///
- [KeyInfo(KeyType.Name | KeyType.Dictionary)]
+ //[KeyInfo(KeyType.Name | KeyType.Dictionary)]
+ [KeyInfo(KeyType.NameOrDictionary | KeyType.Optional)] // #US373
public const string Encoding = "/Encoding";
///
@@ -236,7 +249,6 @@ internal override void PrepareForSave()
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType0Font.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType0Font.cs
index 1332cf79..cb102e11 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType0Font.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType0Font.cs
@@ -2,21 +2,32 @@
// See the LICENSE file in the solution root for more information.
using System.Text;
+using PdfSharp.Internal.OpenType;
using PdfSharp.Fonts;
-using PdfSharp.Fonts.OpenType;
using PdfSharp.Drawing;
+using PdfSharp.Internal;
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Advanced
{
///
/// Represents a composite PDF font. Used for Unicode glyph encoding.
///
- sealed class PdfType0Font : PdfFont
+ public sealed class PdfType0Font : PdfFont
{
public PdfType0Font(PdfDocument document)
: base(document)
{ }
-
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfType0Font(PdfDictionary dict)
+ : base(dict)
+ { }
+
public PdfType0Font(PdfDocument document, XGlyphTypeface glyphTypeface, bool vertical)
: base(document)
{
@@ -24,35 +35,67 @@ public PdfType0Font(PdfDocument document, XGlyphTypeface glyphTypeface, bool ver
Elements.SetName(Keys.Subtype, "/Type0");
Elements.SetName(Keys.Encoding, vertical ? "/Identity-V" : "/Identity-H");
- var otDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(glyphTypeface);
+ var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache;
+ //var otDescriptor = (OpenTypeDescriptor)fontDescriptorCache.GetOrCreateDescriptorFor(glyphTypeface);
+ var otDescriptor = glyphTypeface.OTFontFace.OTDescriptor;
FontDescriptor = document.PdfFontDescriptorCache.GetOrCreatePdfDescriptorFor(otDescriptor, glyphTypeface.GetBaseName());
- //FontOptions = font.PdfOptions;
- //Debug.Assert(FontOptions != null);
-
- _cmapInfo = new CMapInfo(otDescriptor);
+ CMapInfo = new CMapInfo(otDescriptor.FontFace.GlyphCount);
_descendantFont = new PdfCIDFont(document, FontDescriptor /*, font*/)
{
// Base font uses the same cmap info.
- CMapInfo = _cmapInfo
+ CMapInfo = CMapInfo
};
- // Create ToUnicode map
- _toUnicodeMap = new PdfToUnicodeMap(document, _cmapInfo);
+ // Create ToUnicode map.
+ _toUnicodeMap = new PdfToUnicodeMap(document, CMapInfo);
document.Internals.AddObject(_toUnicodeMap);
Elements.Add(Keys.ToUnicode, _toUnicodeMap);
BaseFont = glyphTypeface.GetBaseName();
- // CID fonts are always embedded
+ // CID fonts are always embedded.
BaseFont = FontDescriptor.FontName;
_descendantFont.BaseFont = BaseFont;
PdfArray descendantFonts = new PdfArray(document);
Owner.IrefTable.Add(_descendantFont);
- descendantFonts.Elements.Add(_descendantFont.Reference!); // Reference is set in Add(_descendantFont).
+ descendantFonts.Elements.Add(_descendantFont.RequiredReference); // Reference is set in Add(_descendantFont).
Elements[Keys.DescendantFonts] = descendantFonts;
}
+ //internal PdfType0Font(PdfDocument document, IFontProxy fontProxy, bool vertical) // #PSGFX
+ // : base(document)
+ //{
+ // Elements.SetName(Keys.Type, "/Font");
+ // Elements.SetName(Keys.Subtype, "/Type0");
+ // Elements.SetName(Keys.Encoding, vertical ? "/Identity-V" : "/Identity-H");
+
+ // var otDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(fontProxy);
+ // FontDescriptor = document.PdfFontDescriptorCache.GetOrCreatePdfDescriptorFor(otDescriptor, fontProxy.GetBaseName());
+
+ // _cmapInfo = new CMapInfo(otDescriptor);
+ // _descendantFont = new PdfCIDFont(document, FontDescriptor /*, font*/)
+ // {
+ // // Base font uses the same cmap info.
+ // CMapInfo = _cmapInfo
+ // };
+
+ // // Create ToUnicode map.
+ // _toUnicodeMap = new PdfToUnicodeMap(document, _cmapInfo);
+ // document.Internals.AddObject(_toUnicodeMap);
+ // Elements.Add(Keys.ToUnicode, _toUnicodeMap);
+
+ // BaseFont = fontProxy.GetBaseName();
+ // // CID fonts are always embedded.
+ // BaseFont = FontDescriptor.FontName;
+ // _descendantFont.BaseFont = BaseFont;
+
+ // PdfArray descendantFonts = new PdfArray(document);
+ // Owner.IrefTable.Add(_descendantFont);
+ // descendantFonts.Elements.Add(_descendantFont.Reference!); // Reference is set in Add(_descendantFont).
+ // Elements[Keys.DescendantFonts] = descendantFonts;
+ //}
+
#if true_ // May be superfluous.
public PdfType0Font(PdfDocument document, string idName, byte[] fontData, bool vertical)
: base(document)
@@ -99,7 +142,7 @@ public PdfType0Font(PdfDocument document, string idName, byte[] fontData, bool v
}
#endif
- XPdfFontOptions FontOptions { get; } = default!;
+ XPdfFontOptions FontOptions { get; } = null!;
public string BaseFont
{
@@ -107,9 +150,13 @@ public string BaseFont
set => Elements.SetName(Keys.BaseFont, value);
}
- internal PdfCIDFont DescendantFont => _descendantFont;
+ internal PdfCIDFont DescendantFont
+ {
+ get => _descendantFont;
+ set => _descendantFont = value;
+ }
- readonly PdfCIDFont _descendantFont = default!;
+ PdfCIDFont _descendantFont = null!;
internal override void PrepareForSave()
{
@@ -118,9 +165,9 @@ internal override void PrepareForSave()
// Use GetGlyphIndices to create the widths array.
var descriptor = FontDescriptor.Descriptor;
var w = new StringBuilder("[");
- if (_cmapInfo != null!)
+ if (CMapInfo != null!)
{
- ushort[] glyphIndices = _cmapInfo.GetGlyphIndices();
+ ushort[] glyphIndices = CMapInfo.GetGlyphIndices();
int count = glyphIndices.Length;
int[] glyphWidths = new int[count];
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType1Font.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType1Font.cs
index 8012c067..18d92452 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType1Font.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType1Font.cs
@@ -119,7 +119,7 @@ public PdfType1Font(PdfDocument document)
/// Appendix D) or an encoding dictionary that specifies differences from the font’s
/// built-in encoding or from a specified predefined encoding.
///
- [KeyInfo(KeyType.Name | KeyType.Dictionary)]
+ [KeyInfo(KeyType.NameOrDictionary | KeyType.Optional)]
public const string Encoding = "/Encoding";
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfXObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfXObject.cs
index b850fa07..e788703c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfXObject.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfXObject.cs
@@ -16,6 +16,14 @@ protected PdfXObject(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance from an existing dictionary.
+ /// Used for object type transformation.
+ ///
+ protected PdfXObject(PdfDictionary dict)
+ : base(dict)
+ { }
+
///
/// Predefined keys of this dictionary.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/IAnnotationAppearanceHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/IAnnotationAppearanceHandler.cs
index e74aac4a..c613fe76 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/IAnnotationAppearanceHandler.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/IAnnotationAppearanceHandler.cs
@@ -3,15 +3,18 @@
using PdfSharp.Drawing;
+// v7.0.0 Review
+
namespace PdfSharp.Pdf.Annotations
{
///
- /// Draws the visual representation of an AcroForm element.
+ /// Draws the visual representation of a form element.
///
- public interface IAnnotationAppearanceHandler // kann man Annotation generell selber malen?
+ public interface IAnnotationAppearanceHandler // TODO This is BS
+ // TODO THHO4STLA Make it better.
{
///
- /// Draws the visual representation of an AcroForm element.
+ /// Draws the visual representation of a form element.
///
///
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/Pdf3DAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/Pdf3DAnnotation.cs
new file mode 100644
index 00000000..fc82c1fc
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/Pdf3DAnnotation.cs
@@ -0,0 +1,120 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF 3D annotation.
+ ///
+ public sealed class Pdf3DAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 13.6.2 3D annotations / Page 643
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Pdf3DAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal Pdf3DAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.ThreeD);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required) A 3D stream (see 13.6.3, "3D streams") or 3D reference dictionary
+ /// (see 13.6.3.3, "3D reference dictionaries") that specifies the 3D artwork
+ /// to be shown.
+ ///
+ [KeyInfo(KeyType.StreamOrDictionary | KeyType.Required)]
+ public const string _3DD = "/3DD";
+ ///
+ /// (Optional) An object that specifies the default initial view of the 3D artwork that
+ /// shall be used when the annotation is activated. It may be either a 3D view dictionary
+ /// (see 13.6.4, "3D views") or one of the following types specifying an element in the
+ /// VA array in the 3D stream (see "Table 311 — Entries in a 3D stream dictionary"):
+ /// • An integer specifying an index into the VA array.
+ /// • A text string matching the IN entry in one of the views in the VA array.
+ /// • A name that indicates the first(F), last(L), or default (D) entries in the VA array.
+ /// Default value: the default view in the 3D stream object specified by 3DD.
+ ///
+ [KeyInfo(KeyType.Various | KeyType.Optional)]
+ public const string _3DV = "/3DV";
+
+ ///
+ /// (Optional) An activation dictionary (see "Table 310 — Entries in a 3D activation dictionary")
+ /// that defines the times at which the annotation shall be activated and deactivated and
+ /// the state of the 3D artwork instance at those times.
+ /// Default value: an activation dictionary containing default values for all its entries.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string _3DA = "/3DA";
+
+ ///
+ /// (Optional) A flag indicating the primary use of the 3D annotation. If true, it is intended
+ /// to be interactive; if false, it is intended to be manipulated programmatically, as with
+ /// an ECMAScript animation. Interactive PDF processors may present different user interface
+ /// controls for interactive 3D annotations (for example, to rotate, pan, or zoom the artwork)
+ /// than for those managed by a script or other mechanism.
+ /// Default value: true.
+ ///
+ [KeyInfo(KeyType.Boolean | KeyType.Optional)]
+ public const string _3DI = "/3DI";
+
+ ///
+ /// (Optional) The 3D view box, which is the rectangular area in which the 3D artwork shall
+ /// be drawn. It shall be within the rectangle specified by the annotation’s Rect entry
+ /// and shall be expressed in the annotation’s target coordinate system (see discussion
+ /// following this Table). Default value: the annotation’s Rect entry, expressed in the
+ /// target coordinate system.This value is [-w/2 -h/2 w/2 h/2], where w and h are the width
+ /// and height, respectively, of Rect.
+ ///
+ [KeyInfo(KeyType.Rectangle | KeyType.Optional)]
+ public const string _3DB = "/3DB"; // TODO Check type.
+
+ ///
+ /// (Optional; PDF 2.0) A 3D units dictionary that specifies the units definitions for
+ /// the 3D data associated with this annotation. See "Table 325 — Entries in a 3D units
+ /// dictionary".
+ ///
+ [KeyInfo("2.0", KeyType.Dictionary | KeyType.Optional)]
+ public const string _3DU = "/3DU";
+
+ ///
+ /// (Optional; PDF 2.0) For Geospatial3D requirement type, a geospatial information section
+ /// may be present as an attribute within a 3D Annotation. There are further conditions
+ /// placed on the GPTS and LPTS arrays within the geo-reference coordinate tables to include
+ /// 3D point values. See 12.10.2, "Geospatial measure dictionary".
+ ///
+ [KeyInfo("2.0", KeyType.Dictionary | KeyType.Optional)]
+ public const string GEO = "/GEO";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotation.cs
index db6efe7c..cde7ad89 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotation.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotation.cs
@@ -3,6 +3,11 @@
using PdfSharp.Drawing;
using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.PdfDictionaryExtensions;
+
+// v7.0.0 TODO review
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Annotations
{
@@ -11,6 +16,8 @@ namespace PdfSharp.Pdf.Annotations
///
public abstract class PdfAnnotation : PdfDictionary
{
+ // Reference 2.0: 12.5.2 Annotation dictionaries / Page 467
+
///
/// Initializes a new instance of the class.
///
@@ -21,15 +28,17 @@ protected PdfAnnotation()
///
/// Initializes a new instance of the class.
+ /// The annotation is created as an indirect object.
///
protected PdfAnnotation(PdfDocument document)
- : base(document)
+ : base(document, true)
{
Initialize();
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
internal PdfAnnotation(PdfDictionary dict)
: base(dict)
@@ -38,19 +47,19 @@ internal PdfAnnotation(PdfDictionary dict)
void Initialize()
{
Elements.SetName(Keys.Type, "/Annot");
- Elements.SetString(Keys.NM, Guid.NewGuid().ToString("D"));
- Elements.SetDateTime(Keys.M, DateTime.Now);
+ //Elements.SetString(Keys.NM, Guid.NewGuid().ToString("D"));
+ Elements.SetDateTime(Keys.M, DateTimeOffset.Now);
}
- ///
- /// Removes an annotation from the document
- ///
- ///
- [Obsolete("Use 'Parent.Remove(this)'")]
- public void Delete()
- {
- Parent.Remove(this);
- }
+ /////
+ ///// Removes an annotation from the document
+ /////
+ /////
+ //[Obsolete("Use 'Parent.Remove(this)'")]
+ //public void Delete()
+ //{
+ // Parent_.Remove(this);
+ //}
///
/// Gets or sets the annotation flags of this instance.
@@ -60,62 +69,92 @@ public PdfAnnotationFlags Flags
get => (PdfAnnotationFlags)Elements.GetInteger(Keys.F);
set
{
- Elements.SetInteger(Keys.F, (int)value);
- Elements.SetDateTime(Keys.M, DateTime.Now);
+ Elements.SetIntegerFlag(Keys.F, (int)value);
+ Elements.SetDateTime(Keys.M, DateTimeOffset.Now);
}
}
+ public bool TestFlag(PdfAnnotationFlags flag)
+ {
+ var result = (Flags & flag) == flag;
+ return result;
+ }
+
+ public void SetFlag(PdfAnnotationFlags flag)
+ {
+ Flags |= flag;
+ }
+
+ public void ClearFlag(PdfAnnotationFlags flag)
+ {
+ Flags &= ~flag;
+ }
+
///
/// Gets or sets the PdfAnnotations object that this annotation belongs to.
///
- public PdfAnnotations Parent
+ public PdfAnnotations? Parent
{
get => _parent ?? NRT.ThrowOnNull();
set => _parent = value;
}
-
PdfAnnotations? _parent;
///
- /// Gets or sets the annotation rectangle, defining the location of the annotation
- /// on the page in default user space units.
+ /// Gets or sets the page this annotation belongs to.
///
- public PdfRectangle Rectangle
+ public PdfPage? Page
{
- get => Elements.GetRectangle(Keys.Rect, true);
+ get => Elements.GetObject(Keys.P);
set
{
- Elements.SetRectangle(Keys.Rect, value);
- Elements.SetDateTime(Keys.M, DateTime.Now);
+ if (value != null)
+ Elements.SetObject(Keys.P, value);
+ else
+ Elements.Remove(Keys.P);
}
}
///
- /// Gets or sets the text label to be displayed in the title bar of the annotation’s
- /// pop-up window when open and active. By convention, this entry identifies
- /// the user who added the annotation.
+ /// Gets or sets the annotation rectangle, defining the location of the annotation
+ /// on the page in default user space units.
///
- public string Title
+ public PdfRectangle Rectangle
{
- get => Elements.GetString(Keys.T, true);
+ get => Elements.GetRequiredRectangle(Keys.Rect);
set
{
- Elements.SetString(Keys.T, value);
- Elements.SetDateTime(Keys.M, DateTime.Now);
+ Elements.SetRectangle(Keys.Rect, value);
+ // TODO: Check if update /M is correct.
+ //Elements.SetDateTime(Keys.M, DateTimeOffset.Now);
}
}
- ///
- /// Gets or sets text representing a short description of the subject being
- /// addressed by the annotation.
- ///
- public string Subject
+ // TODO Define the space names
+ public XRect PageRectangle
{
- get => Elements.GetString(Keys.Subj, true);
+ get
+ {
+ if (Page == null)
+ throw new InvalidOperationException("The property PageRectangle requires the Page property to be defined.");
+
+ var pdfRect = Elements.GetRectangle(Keys.Rect);
+ if (pdfRect == null)
+ return XRect.Empty; // TODO Mist!
+
+ return new(pdfRect.X1, Page.Height.Point - pdfRect.Y1 - pdfRect.Height, pdfRect.Width, pdfRect.Height);
+ }
set
{
- Elements.SetString(Keys.Subj, value);
- Elements.SetDateTime(Keys.M, DateTime.Now);
+ if (Page == null)
+ throw new InvalidOperationException("The property PageRectangle requires the Page property to be defined.");
+
+ double y = Page.Height.Point - value.Y - value.Height;
+ var pdfRect = new PdfRectangle(
+ value.X, y,
+ value.X + value.Width, y + value.Height);
+ Elements.SetRectangle(Keys.Rect, pdfRect);
+ Elements.SetDateTime(Keys.M, DateTimeOffset.Now);
}
}
@@ -130,7 +169,7 @@ public string Contents
set
{
Elements.SetString(Keys.Contents, value);
- Elements.SetDateTime(Keys.M, DateTime.Now);
+ Elements.SetDateTime(Keys.M, DateTimeOffset.Now);
}
}
@@ -143,9 +182,10 @@ public XColor Color
{
get
{
- var item = Elements[Keys.C];
- if (item is PdfReference reference)
- item = reference.Value;
+ var item = Elements.GetValue(Keys.C); // #US373
+ //var item = Elements[Keys.C];
+ //if (item is PdfReference reference)
+ // item = reference.Value;
if (item is PdfArray array)
{
if (array.Elements.Count == 3)
@@ -164,7 +204,7 @@ public XColor Color
var array = new PdfArray(Owner, new PdfReal(value.R / 255.0),
new PdfReal(value.G / 255.0), new PdfReal(value.B / 255.0));
Elements[Keys.C] = array;
- Elements.SetDateTime(Keys.M, DateTime.Now);
+ Elements.SetDateTime(Keys.M, DateTimeOffset.Now);
}
}
@@ -187,26 +227,302 @@ public double Opacity
if (value is < 0 or > 1)
throw new ArgumentOutOfRangeException(nameof(value), value, "Opacity must be a value in the range from 0 to 1.");
Elements.SetReal(Keys.CA, value);
- Elements.SetDateTime(Keys.M, DateTime.Now);
+ Elements.SetDateTime(Keys.M, DateTimeOffset.Now);
+ }
+ }
+
+ internal static PdfAnnotation CreateAnnotation(PdfDictionary dict, PdfPage? page = null)
+ {
+ if (dict is PdfAnnotation annotation)
+ return annotation;
+
+ var subtype = dict.Elements.GetName(Keys.Subtype);
+#if true_
+ switch (subtype)
+ {
+ case "/" + nameof(PdfAnnotationTypes.Text):
+ annotation = new PdfTextAnnotation(dict);
+ break;
+
+ case "/" + nameof(PdfAnnotationTypes.Link):
+ annotation = new PdfLinkAnnotation(dict);
+ break;
+
+ case "/" + nameof(PdfAnnotationTypes.FreeText):
+
+ case "/" + nameof(PdfAnnotationTypes.Line):
+ annotation = new PdfLineAnnotation(dict);
+ break;
+
+ case "/" + nameof(PdfAnnotationTypes.Square):
+ annotation = new PdfLineAnnotation(dict);
+ break;
+
+ case "/" + nameof(PdfAnnotationTypes.Circle):
+ annotation = new PdfCircleAnnotation(dict);
+ break;
+
+ case "/" + nameof(PdfAnnotationTypes.Polygon):
+ case "/" + nameof(PdfAnnotationTypes.PolyLine):
+ case "/" + nameof(PdfAnnotationTypes.Highlight):
+ case "/" + nameof(PdfAnnotationTypes.Underline):
+ case "/" + nameof(PdfAnnotationTypes.Squiggly):
+ case "/" + nameof(PdfAnnotationTypes.StrikeOut):
+ case "/" + nameof(PdfAnnotationTypes.Caret):
+ annotation = new PdfGenericAnnotation(dict);
+ break;
+
+ Old code, does not compile.
+ case "/" + nameof(PdfAnnotationTypes.RubberStamp):
+ annotation = new PdfRubberStampAnnotation(dict);
+ break;
+
+ case "/" + nameof(PdfAnnotationTypes.Ink):
+ case "/" + nameof(PdfAnnotationTypes.Popup):
+ case "/" + nameof(PdfAnnotationTypes.FileAttachment):
+ case "/" + nameof(PdfAnnotationTypes.Sound):
+ case "/" + nameof(PdfAnnotationTypes.Movie):
+ case "/" + nameof(PdfAnnotationTypes.Screen):
+ annotation = new PdfGenericAnnotation(dict);
+ break;
+
+ case "/" + nameof(PdfAnnotationTypes.Widget):
+ annotation = CreateWidget();
+ break;
+
+ case "/" + nameof(PdfAnnotationTypes.PrinterMark):
+ case "/" + nameof(PdfAnnotationTypes.TrapNet):
+ case "/" + nameof(PdfAnnotationTypes.Watermark):
+ case "/3D" /*+ nameof(PdfAnnotationTypes.ThreeD)*/:
+ case "/" + nameof(PdfAnnotationTypes.Redact):
+ case "/" + nameof(PdfAnnotationTypes.Projection):
+ case "/" + nameof(PdfAnnotationTypes.RichMedia):
+ annotation = new PdfGenericAnnotation(dict);
+ break;
+
+ default:
+ throw new InvalidOperationException($"Invalid annotation subtype '{subtype}'.");
+ }
+#else
+ annotation = subtype switch
+ {
+ PdfAnnotationTypeNames.Text
+ => new PdfTextAnnotation(dict),
+
+ PdfAnnotationTypeNames.Link
+ => new PdfLinkAnnotation(dict),
+
+ PdfAnnotationTypeNames.FreeText
+ => new PdfFreeTextAnnotation(dict),
+
+ PdfAnnotationTypeNames.Line
+ => new PdfLineAnnotation(dict),
+
+ PdfAnnotationTypeNames.Square
+ => new PdfSquareAnnotation(dict),
+
+ PdfAnnotationTypeNames.Circle
+ => new PdfCircleAnnotation(dict),
+
+ PdfAnnotationTypeNames.Polygon
+ => new PdfPolygonAnnotation(dict),
+
+ PdfAnnotationTypeNames.PolyLine
+ => new PdfPolyLineAnnotation(dict),
+
+ PdfAnnotationTypeNames.Highlight
+ => new PdfHighlightAnnotation(dict),
+
+ PdfAnnotationTypeNames.Underline
+ => new PdfUnderlineAnnotation(dict),
+
+ PdfAnnotationTypeNames.Squiggly
+ => new PdfSquigglyAnnotation(dict),
+
+ PdfAnnotationTypeNames.StrikeOut
+ => new PdfStrikeOutAnnotation(dict),
+
+ PdfAnnotationTypeNames.Caret
+ => new PdfCaretAnnotation(dict),
+
+ PdfAnnotationTypeNames.Stamp
+ => new PdfStampAnnotation(dict),
+
+ PdfAnnotationTypeNames.Ink
+ => new PdfInkAnnotation(dict),
+
+ PdfAnnotationTypeNames.Popup
+ => new PdfPopupAnnotation(dict),
+
+ PdfAnnotationTypeNames.FileAttachment
+ => new PdfFileAttachmentAnnotation(dict),
+
+ PdfAnnotationTypeNames.Sound
+ => new PdfSoundAnnotation(dict),
+
+ PdfAnnotationTypeNames.Movie
+ => new PdfMovieAnnotation(dict),
+
+ PdfAnnotationTypeNames.Screen
+ => new PdfScreenAnnotation(dict),
+
+ PdfAnnotationTypeNames.Widget
+ => new PdfWidgetAnnotation(dict),
+
+ PdfAnnotationTypeNames.PrinterMark
+ => new PdfPrinterMarkAnnotation(dict),
+
+ PdfAnnotationTypeNames.TrapNet
+ => new PdfTrapNetAnnotation(dict),
+
+ PdfAnnotationTypeNames.Watermark
+ => new PdfWatermarkAnnotation(dict),
+
+ PdfAnnotationTypeNames.ThreeD
+ => new Pdf3DAnnotation(dict),
+
+ PdfAnnotationTypeNames.Redact
+ => new PdfRedactAnnotation(dict),
+
+ PdfAnnotationTypeNames.Projection
+ => new PdfProjectionAnnotation(dict),
+
+ PdfAnnotationTypeNames.RichMedia
+ => new PdfRichMediaAnnotation(dict),
+
+ _ => throw new InvalidOperationException($"Invalid annotation subtype '{subtype}'.")
+ };
+#endif
+ if (page != null)
+ annotation.Page = page;
+
+ return annotation;
+
+ //PdfWidgetAnnotation CreateWidget()
+ //{
+ // if (dict is PdfWidgetAnnotation widget)
+ // return widget;
+ // if (dict is PdfFormField field)
+ // return field.GetAsWidgetAnnotation();
+ // return new PdfWidgetAnnotation(dict);
+ //}
+ }
+
+ protected internal static void EnsurePageHasDocument(PdfPage page)
+ {
+ if (page.Document == null)
+ {
+ throw new InvalidOperationException(
+ "You cannot create a PDF annotation for a page that does not yet belong to a document.");
+ }
+ }
+
+ internal string? AppearanceState
+ {
+ get
+ {
+ if (Elements.TryGetName(Keys.AS, out var result))
+ return result;
+ return null;
+ }
+ set
+ {
+ if (value != null)
+ Elements.SetValue(Keys.AS, new PdfName(value));
+ else
+ Elements.Remove(Keys.AS);
}
}
+ ///
+ /// Gets all appearance states for this widget, that are stored in the /N (normal) entry.
+ ///
+ internal List GetAppearanceStateNames()
+ {
+ var subDict = GetAppearanceStreamSubDictionary(PdfAnnotationAppearance.Keys.N);
+ return subDict?.Elements.Keys.ToList() ?? [];
+ }
+
+ ///
+ /// Gets the appearance stream for the given state of this widget, that is stored in the /N (normal) entry.
+ ///
+ /// The name of the appearance state.
+ internal PdfFormXObject? GetNormalAppearanceStream(string appearanceState)
+ {
+ return GetAppearanceStream(PdfAnnotationAppearance.Keys.N, appearanceState);
+ }
+
+ ///
+ /// Gets the appearance stream for the given state of this widget, that are stored in the appearanceKey entry.
+ ///
+ /// The key of the appearance subdictionary: /N (normal), /R (rollover) or /D (down).
+ /// The name of the appearance state.
+ internal PdfFormXObject? GetAppearanceStream(string appearanceKey, string appearanceState)
+ {
+ var subDict = GetAppearanceStreamSubDictionary(appearanceKey);
+
+ return subDict?.Elements.GetDictionary(appearanceState);
+ }
+
+ ///
+ /// Gets the appearance stream subdictionary for this widget, that is stored in the appearanceKey entry.
+ ///
+ /// The key of the appearance subdictionary: /N (normal), /R (rollover) or /D (down).
+ PdfDictionary? GetAppearanceStreamSubDictionary(string appearanceKey)
+ {
+ var appearanceDict = Elements.GetDictionary(Keys.AP);
+ var appearanceStreamOrSubDict = appearanceDict?.Elements.GetObject(appearanceKey);
+
+ if (appearanceStreamOrSubDict is PdfDictionary subDict)
+ return subDict;
+
+ return null;
+ }
+
+ ///
+ /// Gets the appearance stream for this widget, that is stored directly in the /N (normal) entry.
+ ///
+ internal PdfFormXObject? GetNormalAppearanceStream()
+ {
+ return GetAppearanceStream(PdfAnnotationAppearance.Keys.N);
+ }
+
+ ///
+ /// Gets the appearance stream for this widget, that is stored directly in the appearanceKey entry.
+ ///
+ /// The key of the appearance stream: /N (normal), /R (rollover) or /D (down).
+ internal PdfFormXObject? GetAppearanceStream(string appearanceKey)
+ {
+ var appearanceDict = Elements.GetDictionary(Keys.AP);
+ var appearanceStreamOrSubDict = appearanceDict?.Elements.GetObject(appearanceKey);
+
+ if (appearanceStreamOrSubDict is PdfFormXObject stream)
+ return stream;
+
+ return null;
+ }
+
///
/// Predefined keys of this dictionary.
///
public class Keys : KeysBase
{
+ // Updated to:
+ // Reference 2.0: 12.5.2 Annotation dictionaries / Page 467
+ // Reference 2.0: Table 166 — Entries common to all annotation dictionaries / Page 467
+
// ReSharper disable InconsistentNaming
///
- /// (Optional) The type of PDF object that this dictionary describes; if present,
- /// must be Annot for an annotation dictionary.
+ /// (Optional) The type of PDF object that this dictionary describes;
+ /// if present, shall be Annot for an annotation dictionary.
///
[KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Annot")]
public const string Type = "/Type";
///
/// (Required) The type of annotation that this dictionary describes.
+ /// See PdfAnnotationTypes.
///
[KeyInfo(KeyType.Name | KeyType.Required)]
public const string Subtype = "/Subtype";
@@ -219,16 +535,24 @@ public class Keys : KeysBase
public const string Rect = "/Rect";
///
- /// (Optional) Text to be displayed for the annotation or, if this type of annotation
- /// does not display text, an alternate description of the annotation’s contents
- /// in human-readable form. In either case, this text is useful when
- /// extracting the document’s contents in support of accessibility to users with
- /// disabilities or for other purposes.
+ /// (Optional) Text that shall be displayed for the annotation or, if this type of
+ /// annotation does not display text, an alternative description of the annotation’s
+ /// contents in human-readable form. In either case, this text is useful when extracting
+ /// the document’s contents in support of accessibility to users with disabilities or
+ /// for other purposes.
+ /// See PdfAnnotation types for more details on the meaning of this entry for each
+ /// annotation type.
///
[KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Contents = "/Contents";
- // P
+ ///
+ /// (Optional except as noted below; PDF 1.3; not used in FDF files) An indirect reference
+ /// to the page object with which this annotation is associated. This entry shall be present
+ /// in screen annotations associated with rendition actions.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string P = "/P";
///
/// (Optional; PDF 1.4) The annotation name, a text string uniquely identifying it
@@ -238,9 +562,8 @@ public class Keys : KeysBase
public const string NM = "/NM";
///
- /// (Optional; PDF 1.1) The date and time when the annotation was most recently
- /// modified. The preferred format is a date string, but viewer applications should be
- /// prepared to accept and display a string in any format.
+ /// (Optional; PDF 1.1) The date and time when the annotation was most recently modified.
+ /// The format should be a date string.
///
[KeyInfo(KeyType.Date | KeyType.Optional)]
public const string M = "/M";
@@ -253,53 +576,64 @@ public class Keys : KeysBase
public const string F = "/F";
///
- /// (Optional; PDF 1.2) A border style dictionary specifying the characteristics of
- /// the annotation’s border.
+ /// (Optional; PDF 1.2) An appearance dictionary specifying how the annotation shall be presented
+ /// visually on the page. A PDF writer shall include an appearance dictionary when writing or
+ /// updating the PDF file except for the two cases listed below. Every annotation(including those
+ /// whose Subtype value is Widget, as used for form fields), except for the two cases listed below,
+ /// shall have at least one appearance dictionary.
+ /// • Annotations where the value of the Rect key consists of an array where the value at index 1
+ /// is equal to the value at index 3 and the value at index 2 is equal to the value at index 4.
+ /// NOTE(2020)
+ /// The bullet point above was changed from “or” to “and” in this document to match requirements
+ /// in other published ISO PDF standards(such as PDF/A).
+ /// • Annotations whose Subtype value is Popup, Projection or Link.
///
- [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)]
- public const string BS = "/BS";
-
- ///
- /// (Optional; PDF 1.2) An appearance dictionary specifying how the annotation
- /// is presented visually on the page. Individual annotation handlers may ignore
- /// this entry and provide their own appearances.
- ///
- [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)]
+ [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)] // type is PdfAnnotationAppearance
public const string AP = "/AP";
///
/// (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2)
- /// The annotation’s appearance state, which selects the applicable appearance stream from
- /// an appearance subdictionary.
+ /// The annotation’s appearance state, which selects the applicable appearance stream from an
+ /// appearance subdictionary (see 12.5.5, "Appearance streams").
///
- [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)]
+ [KeyInfo("1.2", KeyType.Name | KeyType.Optional)]
public const string AS = "/AS";
///
- /// (Optional) An array specifying the characteristics of the annotation’s border.
- /// The border is specified as a rounded rectangle.
- /// In PDF 1.0, the array consists of three numbers defining the horizontal corner
- /// radius, vertical corner radius, and border width, all in default user space units.
- /// If the corner radii are 0, the border has square (not rounded) corners; if the border
- /// width is 0, no border is drawn.
- /// In PDF 1.1, the array may have a fourth element, an optional dash array defining a
- /// pattern of dashes and gaps to be used in drawing the border. The dash array is
- /// specified in the same format as in the line dash pattern parameter of the graphics state.
- /// For example, a Border value of [0 0 1 [3 2]] specifies a border 1 unit wide, with
- /// square corners, drawn with 3-unit dashes alternating with 2-unit gaps. Note that no
- /// dash phase is specified; the phase is assumed to be 0.
- /// Note: In PDF 1.2 or later, this entry may be ignored in favor of the BS entry.
+ /// (Optional) An array specifying the characteristics of the annotation’s border, which
+ /// shall be drawn as a rounded rectangle.
+ /// (PDF 1.0) The array consists of three numbers defining the horizontal corner radius,
+ /// vertical corner radius, and border width, all in default user space units.If the corner
+ /// radii are 0, the border has square(not rounded) corners; if the border width is 0, no
+ /// border is drawn.
+ /// (PDF 1.1) The array may have a fourth element, an optional dash array defining a pattern
+ /// of dashes and gaps that shall be used in drawing the border.The dash array shall be
+ /// specified in the same format as in the line dash pattern parameter of the graphics
+ /// state(see 8.4.3.6, "Line dash pattern"). The dash phase shall not be specified and
+ /// shall be assumed to be 0.
+ /// EXAMPLE A Border value of[0 0 1[3 2]] specifies a border 1 unit wide, with square corners,
+ /// drawn with 3-unit dashes alternating with 2- unit gaps.
+ /// NOTE
+ /// (PDF 1.2) The dictionaries for some annotation types (such as free text and polygon
+ /// annotations) can include the BS entry.That entry specifies a border style dictionary
+ /// that has more settings than the array specified for the Border entry.If an annotation
+ /// dictionary includes the BS entry, then the Border entry is ignored.
+ /// Default value: [0 0 1].
///
[KeyInfo(KeyType.Array | KeyType.Optional)]
public const string Border = "/Border";
///
- /// (Optional; PDF 1.1) An array of three numbers in the range 0.0 to 1.0, representing
- /// the components of a color in the DeviceRGB color space. This color is used for the
- /// following purposes:
- /// • The background of the annotation’s icon when closed
- /// • The title bar of the annotation’s pop-up window
- /// • The border of a link annotation
+ /// (Optional; PDF 1.1) An array of numbers in the range 0.0 to 1.0, representing a colour
+ /// used for the following purposes:
+ /// The background of the annotation’s icon when closed
+ /// The title bar of the annotation’s popup window
+ /// The border of a link annotation
+ /// The number of array elements determines the colour space in which the colour shall be defined:
+ /// 0 No colour; transparent
+ /// 1 DeviceGray
+ /// 3 DeviceRGB
+ /// 4 DeviceCMYK
///
[KeyInfo("1.1", KeyType.Array | KeyType.Optional)]
public const string C = "/C";
@@ -313,19 +647,84 @@ public class Keys : KeysBase
public const string StructParent = "/StructParent";
///
- /// (Optional; PDF 1.1) An action to be performed when the annotation is activated.
- /// Note: This entry is not permitted in link annotations if a Dest entry is present.
- /// Also note that the A entry in movie annotations has a different meaning.
+ /// (Optional; PDF 1.5) An optional content group or optional content membership dictionary
+ /// specifying the optional content properties for the annotation. Before the annotation
+ /// is drawn, its visibility shall be determined based on this entry as well as the annotation
+ /// flags specified in the F entry. If it is determined to be invisible, the annotation shall
+ /// not be drawn.
///
- [KeyInfo("1.1", KeyType.Dictionary | KeyType.Optional)]
- public const string A = "/A";
+ [KeyInfo("1.5", KeyType.Dictionary | KeyType.Optional)]
+ public const string OC = "/OC";
- // AA
- // StructParent
- // OC
+ ///
+ /// (Optional; PDF 2.0) An array of one or more file specification dictionaries which
+ /// denote the associated files for this annotation.
+ ///
+ [KeyInfo("2.0", KeyType.ArrayOfDictionaries | KeyType.Optional)]
+ public const string AF = "/AF";
+
+ ///
+ /// (Optional; PDF 2.0) When regenerating the annotation's appearance stream,
+ /// this is the opacity value (11.2, "Overview of transparency") that shall be used
+ /// for all nonstroking operations on all visible elements of the annotation in its
+ /// closed state (including its background and border) but not the popup window that
+ /// appears when the annotation is opened.
+ /// Default value: 1.0
+ /// The specified value shall not be used if the annotation has an appearance stream;
+ /// in that case, the appearance stream shall specify any transparency.
+ /// If no explicit appearance stream is defined for the annotation, and the processor is not
+ /// able to regenerate the appearance, the annotation may be painted by implementation-dependent
+ /// means that do not necessarily conform to the PDF imaging
+ ///
+ [KeyInfo("2.0", KeyType.Integer | KeyType.Optional)]
+ public const string ca = "/ca";
+
+ ///
+ /// (Optional; PDF 1.4, PDF 2.0 for non-markup annotations) When regenerating the annotation's
+ /// appearance stream, this is the opacity value (11.2, "Overview of transparency") that shall
+ /// be used for stroking all visible elements of the annotation in its closed state, including
+ /// its background and border, but not the popup window that appears when the annotation is
+ /// opened.
+ /// If a ca entry is not present in this dictionary, then the value of this CA entry shall also
+ /// be used for nonstroking operations as well.
+ /// Default Value: 1.0
+ /// The specified value shall not be used if the annotation has an appearance stream;
+ /// in that case, the appearance stream shall specify any transparency.
+ /// If no explicit appearance stream is defined for the annotation, and the processor is not
+ /// able to regenerate the appearance, the annotation may be painted by implementation-dependent
+ /// means that do not necessarily conform to the PDF imaging model; in this case, the effect of
+ /// this entry is implementation-dependent as well.
+ ///
+ [KeyInfo("1.4", KeyType.Integer | KeyType.Optional)]
+ public const string CA = "/CA";
+
+ ///
+ /// (Optional; PDF 2.0) The blend mode that shall be used when painting the annotation onto the
+ /// page. If this key is not present, blending shall take place using the Normal blend mode. The
+ /// value shall be a name object, designating one of the standard blend modes listed in
+ /// "Table 134 — Standard separable blend modes" and
+ /// "Table 135 — Standard non-separable blend modes".
+ ///
+ [KeyInfo("2.0", KeyType.Name | KeyType.Optional)]
+ public const string BM = "/BM";
+
+ ///
+ /// (Optional; PDF 2.0) A language identifier overriding the document’s language identifier to
+ /// specify the natural language for all text in the annotation except where overridden by other
+ /// explicit language specifications.
+ ///
+ [KeyInfo("2.0", KeyType.TextString | KeyType.Optional)]
+ public const string Lang = "/Lang";
+
+ /* Excerpt from the specs:
+ * A PDF reader shall render the appearance dictionary without regard to any other keys and values in
+ * the annotation dictionary and shall ignore the values of the
+ * C, IC, Border, BS, BE, BM, CA, ca, H, DA, Q, DS, LE, LL, LLE, and Sy keys.
+ */
// ----- Excerpt of entries specific to markup annotations ----------------------------------
+#if true_ // TODO Remove this. See PdfMarkupAnnotation.
///
/// (Optional; PDF 1.1) The text label to be displayed in the title bar of the annotation’s
/// pop-up window when open and active. By convention, this entry identifies
@@ -341,20 +740,20 @@ public class Keys : KeysBase
[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
public const string Popup = "/Popup";
- ///
- /// (Optional; PDF 1.4) The constant opacity value to be used in painting the annotation.
- /// This value applies to all visible elements of the annotation in its closed state
- /// (including its background and border) but not to the popup window that appears when
- /// the annotation is opened.
- /// The specified value is not used if the annotation has an appearance stream; in that
- /// case, the appearance stream must specify any transparency. (However, if the viewer
- /// regenerates the annotation’s appearance stream, it may incorporate the CA value
- /// into the stream’s content.)
- /// The implicit blend mode is Normal.
- /// Default value: 1.0.
- ///
- [KeyInfo(KeyType.Real | KeyType.Optional)]
- public const string CA = "/CA";
+ /////
+ ///// (Optional; PDF 1.4) The constant opacity value to be used in painting the annotation.
+ ///// This value applies to all visible elements of the annotation in its closed state
+ ///// (including its background and border) but not to the popup window that appears when
+ ///// the annotation is opened.
+ ///// The specified value is not used if the annotation has an appearance stream; in that
+ ///// case, the appearance stream must specify any transparency. (However, if the viewer
+ ///// regenerates the annotation’s appearance stream, it may incorporate the CA value
+ ///// into the stream’s content.)
+ ///// The implicit blend mode is Normal.
+ ///// Default value: 1.0.
+ /////
+ //[KeyInfo(KeyType.Real | KeyType.Optional)]
+ //public const string CA = "/CA";
//RC
//CreationDate
@@ -369,7 +768,17 @@ public class Keys : KeysBase
//RT
//IT
+#endif
// ReSharper restore InconsistentNaming
+
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
}
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotationAppearance.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotationAppearance.cs
new file mode 100644
index 00000000..ab687f17
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotationAppearance.cs
@@ -0,0 +1,120 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.PdfDictionaryExtensions;
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents the border effect of all annotations.
+ ///
+ public sealed class PdfAnnotationAppearance : PdfDictionary
+ {
+ // Reference 2.0: 12.5.5 Appearance streams / Page 474
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfAnnotationAppearance(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfAnnotationAppearance(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ public Dictionary? GetNValues()
+ {
+ return GetValues("/N");
+ }
+
+ public Dictionary? GetRValues()
+ {
+ return GetValues("/R");
+ }
+
+ public Dictionary? GetDValues()
+ {
+ return GetValues("/D");
+ }
+
+ Dictionary? GetValues(string type)
+ {
+ var dict = Elements.GetDictionary(type);
+ if (dict == null)
+ return null;
+
+ Dictionary result = new();
+
+ if (PdfFormXObject.IsFormXObject(dict))
+ {
+ Transform("/", dict);
+ }
+ else
+ {
+ var keys = dict.Elements.Keys;
+ foreach (var key in keys)
+ {
+ //if (dict.Elements[key] is PdfDictionary stream && PdfFormXObject.IsFormXObject(stream))
+ if (dict.Elements.GetValue(key) is PdfDictionary stream && PdfFormXObject.IsFormXObject(stream)) // #US373
+ {
+ Transform(key, stream);
+ }
+ }
+ }
+ return result;
+
+ void Transform(string key, PdfDictionary d)
+ {
+ var form = (PdfFormXObject)dict.Transform(typeof(PdfFormXObject));
+ result.Add(key, form);
+ }
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : KeysBase
+ {
+ // Reference 2.0: Table 170 — Entries in an appearance dictionary / Page 475
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required) The annotation’s normal appearance.
+ ///
+ [KeyInfo(KeyType.StreamOrDictionary | KeyType.Required)]
+ public const string N = "/N";
+
+ ///
+ /// (Optional) The annotation’s rollover appearance. Default value: the value of the N entry.
+ ///
+ [KeyInfo(KeyType.StreamOrDictionary | KeyType.Optional)]
+ public const string R = "/R";
+
+ ///
+ /// (Optional) The annotation’s down appearance. Default value: the value of the N entry.
+ ///
+ [KeyInfo(KeyType.StreamOrDictionary | KeyType.Optional)]
+ public const string D = "/D";
+
+ // ReSharper restore InconsistentNaming
+
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotations.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotations.cs
index 45f1d8dc..0541b391 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotations.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotations.cs
@@ -1,9 +1,12 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using System.Collections;
+using PdfSharp.Pdf.Forms;
using PdfSharp.Pdf.Advanced;
+// v7.0.0 TODO review
+
namespace PdfSharp.Pdf.Annotations
{
///
@@ -11,6 +14,8 @@ namespace PdfSharp.Pdf.Annotations
///
public sealed class PdfAnnotations : PdfArray
{
+ // Reference 2.0: /Annots key in table 31 — Entries in a page object / Page 105
+
internal PdfAnnotations(PdfDocument document)
: base(document)
{ }
@@ -26,8 +31,8 @@ internal PdfAnnotations(PdfArray array)
public void Add(PdfAnnotation annotation)
{
annotation.Document = Owner;
- Owner.IrefTable.Add(annotation);
- Elements.Add(annotation.ReferenceNotNull);
+ annotation.Page = Page;
+ Elements.Add(annotation.RequiredReference);
}
///
@@ -38,8 +43,7 @@ public void Remove(PdfAnnotation annotation)
if (annotation.Owner != Owner)
throw new InvalidOperationException("The annotation does not belong to this document.");
- Owner.Internals.RemoveObject(annotation);
- Elements.Remove(annotation.ReferenceNotNull);
+ Elements.Remove(annotation.RequiredReference);
}
///
@@ -48,15 +52,10 @@ public void Remove(PdfAnnotation annotation)
public void Clear()
{
for (int idx = Count - 1; idx >= 0; idx--)
- Page.Annotations.Remove(Page.Annotations[idx]);
+ //Page.Annotations.Remove(Page.Annotations[idx]); // ???
+ Elements.RemoveAt(idx);
}
- //public void Insert(int index, PdfAnnotation annotation)
- //{
- // annotation.Document = Document;
- // annotations.Insert(index, annotation);
- //}
-
///
/// Gets the number of annotations in this collection.
///
@@ -85,7 +84,8 @@ public PdfAnnotation this[int index]
var annotation = dict as PdfAnnotation;
if (annotation == null)
{
- annotation = new PdfGenericAnnotation(dict);
+ //annotation = new PdfGenericAnnotation(dict);
+ annotation = PdfAnnotation.CreateAnnotation(dict, Page);
if (iref == null)
Elements[index] = annotation;
}
@@ -93,22 +93,6 @@ public PdfAnnotation this[int index]
}
}
- //public PdfAnnotation this[int index]
- //{
- // get
- // {
- // //DMH 6/7/06
- // //Broke this out to simplify debugging
- // //Use a generic annotation to access the metadata
- // //Assign this as the parent of the annotation
- // PdfReference r = Elements[index] as PdfReference;
- // PdfDictionary d = r.Value as PdfDictionary;
- // PdfGenericAnnotation a = new PdfGenericAnnotation(d);
- // a.Collection = this;
- // return a;
- // }
- //}
-
///
/// Gets the page the annotations belongs to.
///
@@ -119,61 +103,115 @@ internal PdfPage Page
}
PdfPage? _page;
+ internal void DeriveInstances()
+ {
+ int count = Elements.Count;
+ for (int idx = 0; idx < count; idx++)
+ {
+ var dict = Elements.GetRequiredDictionary(idx);
+ if (dict is PdfAnnotation)
+ continue;
+ dict = PdfAnnotation.CreateAnnotation(dict, _page);
+ Elements[idx] = dict;
+ }
+ }
+
///
/// Fixes the /P element in imported annotation.
///
internal static void FixImportedAnnotation(PdfPage page)
{
- var annots = page.Elements.GetArray(PdfPage.Keys.Annots);
- if (annots != null)
+ var annotations = page.Elements.GetArray(PdfPage.Keys.Annots);
+ if (annotations != null)
{
- int count = annots.Elements.Count;
+ int count = annotations.Elements.Count;
for (int idx = 0; idx < count; idx++)
{
- var annot = annots.Elements.GetDictionary(idx);
- if (annot != null && annot.Elements.ContainsKey("/P"))
- annot.Elements["/P"] = page.Reference;
+ var annot = annotations.Elements.GetDictionary(idx);
+ if (annot != null && annot.Elements.ContainsKey(PdfAnnotation.Keys.P))
+ annot.Elements[PdfAnnotation.Keys.P] = page;
}
}
+ // DELETE
+ //var annots = page.Elements.GetArray(PdfPage.Keys.Annots);
+ //if (annots != null)
+ //{
+ // int count = annots.Elements.Count;
+ // for (int idx = 0; idx < count; idx++)
+ // {
+ // var annot = annots.Elements.GetDictionary(idx);
+ // if (annot != null && annot.Elements.ContainsKey("/P"))
+ // annot.Elements["/P"] = page.RequiredReference;
+ // }
+ //}
}
///
/// Returns an enumerator that iterates through a collection.
///
public override IEnumerator GetEnumerator()
- {
- return (IEnumerator)new AnnotationsIterator(this);
- }
+ => new AnnotationsIterator(this);
- class AnnotationsIterator : IEnumerator
+ class AnnotationsIterator(PdfAnnotations annotations) : IEnumerator
{
- public AnnotationsIterator(PdfAnnotations annotations)
- {
- _annotations = annotations;
- _index = -1;
- }
-
- public PdfItem/*PdfAnnotation*/ Current => _annotations[_index];
+ public PdfItem/*PdfAnnotation*/ Current => annotations[_index];
object IEnumerator.Current => Current;
- public bool MoveNext()
- {
- return ++_index < _annotations.Count;
- }
+ public bool MoveNext() => ++_index < annotations.Count;
- public void Reset()
- {
- _index = -1;
- }
+ public void Reset() => _index = -1;
public void Dispose()
+ { }
+
+ int _index = -1;
+ }
+
+ internal static class AnnotationPreparer
+ {
+ public static void PrepareDocument(PdfDocument doc)
{
- //throw new NotImplementedException();
+ CreateAnnotationObjects(doc);
}
- readonly PdfAnnotations _annotations;
- int _index;
+ static void CreateAnnotationObjects(PdfDocument doc)
+ {
+ var pages = doc.Catalog.Pages;
+ foreach (var page in pages)
+ {
+ var annots = page.Elements.GetArray(PdfPage.Keys.Annots);
+ if (annots != null)
+ {
+ var count = annots.Elements.Count;
+ for (int idx = 0; idx < count; idx++)
+ {
+ var annot = annots.Elements.GetDictionary(idx);
+
+ // Already handled in Acro fields.
+ if (annot is PdfWidgetAnnotation widget)
+ continue;
+
+ Debug.Assert(annot is not PdfFormField);
+
+ if (annot != null)
+ {
+ Debug.Assert(annot.IsIndirect);
+
+ var type = annot.GetType();
+ if (type != typeof(PdfDictionary))
+ throw new InvalidOperationException("Not a dictionary???");
+
+ PdfAnnotation.CreateAnnotation(annot);
+ }
+ else
+ {
+ throw new InvalidOperationException("Not an annotation???");
+ }
+ }
+ }
+ }
+ }
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderEffect.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderEffect.cs
new file mode 100644
index 00000000..f285fb52
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderEffect.cs
@@ -0,0 +1,82 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents the border effect of all annotations.
+ ///
+ public class PdfBorderEffect : PdfDictionary
+ {
+ // Reference 2.0: 12.5.4 Border styles / Page 472
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfBorderEffect()
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfBorderEffect(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfBorderEffect(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ // TODO
+ //Elements.SetName(Keys.Type, "/Annot");
+ //Elements.SetDateTime(Keys.M, DateTimeOffset.Now);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : KeysBase
+ {
+ // Reference 2.0: Table 168 — Entries in a border style dictionary / Page 473
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) A name representing the border effect to apply. Values are:
+ /// C The border should appear "cloudy"; that is, the border should be drawn as a series
+ /// of convex curved line segments in a manner that simulates the appearance of a cloud.
+ /// The width and dash array specified by BS shall be honoured.
+ /// Default value: S.
+ /// S No effect: the border shall be as described by the annotation dictionary’s BS entry.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string S = "/S";
+
+ ///
+ /// (Optional; valid only if the value of S is C) A number describing the intensity of the
+ /// effect, in the range 0 to 2. Default value: 0.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string I = "/I";
+
+ // ReSharper restore InconsistentNaming
+
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderStyle.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderStyle.cs
new file mode 100644
index 00000000..10b1bba6
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderStyle.cs
@@ -0,0 +1,103 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents the border styles of all annotations.
+ ///
+ public class PdfBorderStyle : PdfDictionary
+ {
+ // Reference 2.0: 12.5.4 Border styles / Page 472
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfBorderStyle()
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfBorderStyle(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfBorderStyle(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(Keys.Type, "/Border");
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : KeysBase
+ {
+ // Reference 2.0: Table 168 — Entries in a border style dictionary / Page 473
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) The type of PDF object that this dictionary describes; if present,
+ /// shall be Border for a border style dictionary.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Border")]
+ public const string Type = "/Type";
+
+ ///
+ /// (Optional) The border width in points. If this value is 0, no border shall be drawn.
+ /// Default value: 1.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string W = "/W";
+
+ ///
+ /// (Optional) The border style:
+ /// S (Solid) A solid rectangle surrounding the annotation.Default value.
+ /// D (Dashed) A dashed rectangle surrounding the annotation.The dash pattern may be specified by the D entry.
+ /// B (Beveled) A simulated embossed rectangle that appears to be raised above the surface of the page.
+ /// I (Inset) A simulated engraved rectangle that appears to be recessed below the surface of the page.
+ /// U (Underline) A single line along the bottom of the annotation rectangle.
+ /// An interactive PDF processor shall tolerate other border styles that it does not recognise
+ /// and shall use the default value (which is S).
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string S = "/S";
+
+ ///
+ /// (Optional) A dash array defining a pattern of dashes and gaps that shall be used in drawing
+ /// a dashed border (border style D in the S entry). The dash array shall be specified in the
+ /// same format as in the line dash pattern parameter of the graphics state. The dash phase
+ /// shall not be specified and shall be assumed to be 0.
+ /// EXAMPLE
+ /// A D entry of[3 2] specifies a border drawn with 3-point dashes alternating with 2-point gaps.
+ /// Default value: [3].
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string D = "/D";
+
+ // ReSharper restore InconsistentNaming
+
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfCaretAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfCaretAnnotation.cs
new file mode 100644
index 00000000..d6331ae1
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfCaretAnnotation.cs
@@ -0,0 +1,80 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF caret annotation.
+ ///
+ public sealed class PdfCaretAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.11 Caret annotations / Page 493
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfCaretAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfCaretAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Caret);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ // Reference 2.0: Table 183 — Additional entries specific to a caret annotation / Page 493
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional; PDF 1.5) A set of four numbers that shall describe the numerical differences
+ /// between two rectangles: the Rect entry of the annotation and the actual boundaries
+ /// of the underlying caret. Such a difference can occur. When a paragraph symbol specified
+ /// by Sy is displayed along with the caret.
+ /// The four numbers shall correspond to the differences in default user space between
+ /// the left, top, right, and bottom coordinates of Rect and those of the caret, respectively.
+ /// Each value shall be greater than or equal to 0. The sum of the top and bottom differences
+ /// shall be less than the height of Rect, and the sum of the left and right differences
+ /// shall be less than the width of Rect.
+ ///
+ //[KeyInfo("1.5", KeyType.Rectangle | KeyType.Optional)]
+ [KeyInfo("1.5", KeyType.Array | KeyType.Optional)]
+ public const string RD = "/RD"; // left, top, right, bottom
+
+ ///
+ /// (Optional) A name specifying a symbol that shall be associated with the caret:
+ /// P A new paragraph symbol(¶) shall be associated with the caret.
+ /// None No symbol shall be associated with the caret.
+ /// Default value: None.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Sy = "/Sy";
+
+ // ReSharper restore InconsistentNaming
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFileAttachmentAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFileAttachmentAnnotation.cs
new file mode 100644
index 00000000..34c6dbd8
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFileAttachmentAnnotation.cs
@@ -0,0 +1,66 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF file attachment annotation.
+ ///
+ public sealed class PdfFileAttachmentAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.15 File attachment annotations / Page 496
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfFileAttachmentAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFileAttachmentAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.FileAttachment);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ ///
+ /// (Required) The file associated with this annotation.
+ ///
+ [KeyInfo(KeyType.FileSpecification | KeyType.Required)]
+ public const string FS = "/FS";
+
+ ///
+ /// (Optional) The name of an icon that shall be used in displaying the annotation.
+ /// PDF writers should include this entry and PDF readers should provide predefined
+ /// icon appearances for at least the following standard names:
+ /// Graph, PushPin, Paperclip, Tag
+ /// Additional names may be supported as well.Default value: PushPin.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Name = "/Name";
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFreeTextAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFreeTextAnnotation.cs
new file mode 100644
index 00000000..7df0d0b9
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFreeTextAnnotation.cs
@@ -0,0 +1,153 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF free text annotation.
+ ///
+ public sealed class PdfFreeTextAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.6 Free text annotations / Page 484
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfFreeTextAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFreeTextAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.FreeText);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ // ReSharper disable InconsistentNaming
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ ///
+ /// (Required) The default appearance string that shall be used in formatting the text.
+ /// The annotation dictionary’s AP entry, if present, shall take precedence over the DA
+ /// entry.
+ ///
+ [KeyInfo(KeyType.String | KeyType.Required)]
+ public const string DA = "/DA";
+
+ ///
+ /// (Optional; PDF 1.4) A code specifying the form of quadding (justification) that shall
+ /// be used in displaying the annotation’s text:
+ /// 0 Left-justified
+ /// 1 Centred
+ /// 2 Right-justified
+ /// Default value: 0 (left-justified).
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string Q = "/Q";
+
+ ///
+ /// (Optional; PDF 1.5) A rich text string that shall be used to generate the appearance
+ /// of the annotation.
+ /// NOTE As freetext annotations do not have an open state this cannot apply to the popup
+ /// window as described for the RC key in "Table 172 — Additional entries in an annotation
+ /// dictionary specific to markup annotations".
+ ///
+ //[KeyInfo(KeyType.TextString | KeyType.Stream | KeyType.Optional)] // @@@@STLA
+ [KeyInfo(KeyType.TextStringOrStream | KeyType.Optional)] // @@@@STLA // #US373
+ public const string RC = "/RC";
+
+ ///
+ /// (Optional; PDF 1.5) A default style string, as described in Adobe XML Architecture,
+ /// XML Forms Architecture (XFA) Specification, version 3.3.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string DS = "/DS";
+
+ ///
+ /// (Optional; meaningful only if IT is FreeTextCallout; PDF 1.6) An array of four or six
+ /// numbers specifying a callout line attached to the free text annotation. Six numbers
+ /// [x1 y1 x2 y2 x3 y3] represent the starting, knee point, and ending coordinates of the
+ /// line in default user space. Four numbers [x1 y1 x2 y2] represent the starting and ending
+ /// coordinates of the line.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string CL = "/CL";
+
+ ///
+ /// (Optional; PDF 1.6) A name describing the intent of the free text annotation. The following
+ /// values shall be valid:
+ /// FreeText The annotation is intended to function as a plain free-text annotation.A plain
+ /// free-text annotation is also known as a text box comment.
+ /// FreeTextCallout The annotation is intended to function as a callout. The callout is
+ /// associated with an area on the page through the callout line specified in CL.
+ /// FreeTextTypeWriter The annotation is intended to function as a click-to-type or typewriter
+ /// object and no callout line is drawn.
+ /// Default value: FreeText
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string IT = "/IT";
+
+ ///
+ /// (Optional; PDF 1.6) A border effect dictionary used in conjunction with the border style
+ /// dictionary specified by the BS entry.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string BE = "/BE";
+
+ ///
+ /// (Optional; PDF 1.6) A set of four numbers describing the numerical differences between
+ /// two rectangles: the Rect entry of the annotation and a rectangle contained within that
+ /// rectangle. The inner rectangle is where the annotation’s text should be displayed.
+ /// Any border styles and/or border effects specified by BS and BE entries, respectively,
+ /// shall be applied to the border of the inner rectangle.
+ /// The four numbers correspond to the differences in default user space between the left,
+ /// top, right, and bottom coordinates of Rect and those of the inner rectangle, respectively.
+ /// Each value shall be greater than or equal to 0. The sum of the top and bottom differences
+ /// shall be less than the height of Rect, and the sum of the left and right differences
+ /// shall be less than the width of Rect.
+ ///
+ [KeyInfo(KeyType.Rectangle | KeyType.Optional)]
+ public const string RD = "/RD";
+
+ ///
+ /// (Optional; PDF 1.6) A border style dictionary specifying the line width and dash pattern
+ /// that shall be used in drawing the annotation’s border.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string BS = "/BS";
+
+ ///
+ /// (Optional; meaningful only if CL is present; PDF 1.6) A name specifying the line ending
+ /// style that shall be used in drawing the callout line specified in CL. The name shall
+ /// specify the line ending style for the endpoint defined by the pairs of coordinates (x1, y1).
+ /// Default value: None.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string LE = "/LE";
+
+ // ReSharper restore InconsistentNaming
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfGenericAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfGenericAnnotation.cs
deleted file mode 100644
index a2e5f5c9..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfGenericAnnotation.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-namespace PdfSharp.Pdf.Annotations
-{
- ///
- /// Represents a generic annotation. Used for annotation dictionaries unknown to PDFsharp.
- ///
- sealed class PdfGenericAnnotation : PdfAnnotation
- {
- // DMH 6/7/06
- // Make this public so we can use it in PdfAnnotations to
- // get the metadata from existing annotations.
- public PdfGenericAnnotation(PdfDictionary dict)
- : base(dict)
- { }
-
- ///
- /// Predefined keys of this dictionary.
- ///
- internal new class Keys : PdfAnnotation.Keys
- {
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
- static DictionaryMeta? _meta;
- }
-
- ///
- /// Gets the KeysMeta of this dictionary type.
- ///
- internal override DictionaryMeta Meta => Keys.Meta;
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfInkAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfInkAnnotation.cs
new file mode 100644
index 00000000..368bf82a
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfInkAnnotation.cs
@@ -0,0 +1,82 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF ink annotation.
+ ///
+ public sealed class PdfInkAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.13 Ink annotations / Page 494
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfInkAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfInkAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Ink);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required) An array of n arrays, each representing a stroked path. Each array shall
+ /// be a series of alternating horizontal and vertical coordinates in default user space,
+ /// specifying points along the path. When drawn, the points shall be connected by straight
+ /// lines or curves in an implementation-dependent way.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Required)]
+ public const string InkList = "/InkList";
+
+ ///
+ /// (Optional) A border style dictionary (see "Table 168 — Entries in a border style dictionary")
+ /// specifying the line width and dash pattern that shall be used in drawing the paths.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string BS = "/BS";
+
+ ///
+ /// (Optional; PDF 2.0) An array of n arrays, each supplying the operands for a path building
+ /// operator (m, l or c). Each of the n arrays shall contain pairs of values specifying
+ /// the points(x and y values) for a path drawing operation. The first array shall be of
+ /// length 2 and specifies the operand of a moveto operator which establishes a current point.
+ /// Subsequent arrays of length 2 specify the operands of lineto operators. Arrays of length
+ /// 6 specify the operands for curveto operators. Each array is processed in sequence to
+ /// construct the path. The current graphics state shall control the path width,
+ /// dash pattern, etc.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string Path = "/Path";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLineAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLineAnnotation.cs
new file mode 100644
index 00000000..52dbf1d8
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLineAnnotation.cs
@@ -0,0 +1,166 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF Line annotation.
+ ///
+ public sealed class PdfLineAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.7 Line annotations / Page 486
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfLineAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfLineAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Line);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ // ReSharper disable InconsistentNaming
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ ///
+ /// (Required) An array of four numbers, [x1 y1 x2 y2], specifying the starting and ending
+ /// coordinates of the line in default user space.
+ /// If the LL entry is present, this value shall represent the endpoints of the leader lines
+ /// rather than the endpoints of the line itself.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Required)]
+ public const string L = "/L";
+
+ ///
+ /// (Optional) A border style dictionary specifying the width and dash pattern that shall
+ /// be used in drawing the line.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string BS = "/BS";
+
+ ///
+ /// (Optional; PDF 1.4) An array of two names specifying the line ending styles that shall
+ /// be used in drawing the line. The first and second elements of the array shall specify
+ /// the line ending styles for the endpoints defined, respectively, by the first and second
+ /// pairs of coordinates, (x1, y1 ) and (x2, y2 ), in the L array.
+ /// "Table 179 — Line ending styles" shows the permitted values. Default value: [ /None /None ].
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string LE = "/LE";
+
+ ///
+ /// (Optional; PDF 1.4) An array of numbers in the range 0.0 to 1.0 specifying the interior
+ /// colour that shall be used to fill the annotation’s line endings. The number of array
+ /// elements shall determine the colour space in which the colour is defined:
+ /// 0 No colour; transparent
+ /// 1 DeviceGray
+ /// 3 DeviceRGB
+ /// 4 DeviceCMYK
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string IC = "/IC";
+
+ ///
+ /// (Required if LLE is present, otherwise optional; PDF 1.6) The length of leader lines
+ /// in default user space that extend from each endpoint of the line perpendicular to the
+ /// line itself. A positive value shall mean that the leader lines appear in the direction
+ /// that is clockwise when traversing the line from its starting point to its ending point
+ /// (as specified by L); a negative value shall indicate the opposite direction.
+ /// Default value: 0 (no leader lines).
+ ///
+ [KeyInfo(KeyType.Real | KeyType.Required)]
+ public const string LL = "/LL";
+
+ ///
+ /// (Optional; PDF 1.6) A non-negative number that shall represent the length of leader
+ /// line extensions that extend from the line proper 180 degrees from the leader lines.
+ /// Default value: 0 (no leader line extensions).
+ ///
+ [KeyInfo(KeyType.Real | KeyType.Optional)]
+ public const string LLE = "/LLE";
+
+ ///
+ /// (Optional; PDF 1.6) If true, the text specified by the Contents or RC entries shall
+ /// be replicated as a caption in the appearance of the line. The text shall be rendered
+ /// in a manner appropriate to the content, taking into account factors such as writing
+ /// direction.
+ /// Default value: false.
+ ///
+ [KeyInfo(KeyType.Boolean | KeyType.Optional)]
+ public const string Cap = "/Cap";
+
+ ///
+ /// (Optional; PDF 1.6) A name describing the intent of the line annotation. Valid values
+ /// shall be LineArrow, which means that the annotation is intended to function as an arrow,
+ /// and LineDimension, which means that the annotation is intended to function as a dimension
+ /// line.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string IT = "/IT";
+
+ ///
+ /// (Optional; PDF 1.7) A non-negative number that shall represent the length of the leader
+ /// line offset, which is the amount of empty space between the endpoints of the annotation
+ /// and the beginning of the leader lines.
+ ///
+ [KeyInfo(KeyType.Real | KeyType.Optional)]
+ public const string LLO = "/LLO";
+
+ ///
+ /// (Optional; meaningful only if Cap is true; PDF 1.7) A name describing the annotation’s
+ /// caption positioning. Valid values are Inline, meaning the caption shall be centred inside
+ /// the line, and Top, meaning the caption shall be on top of the line.
+ /// Default value: Inline
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string CP = "/CP";
+
+ ///
+ /// (Optional; PDF 1.7) A measure dictionary that shall specify the scale and units that
+ /// apply to the line annotation.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string Measure = "/Measure";
+
+ ///
+ /// (Optional; meaningful only if Cap is true; PDF 1.7) An array of two numbers that shall
+ /// specify the offset of the caption text from its normal position. The first value shall
+ /// be the horizontal offset along the annotation line from its midpoint, with a positive
+ /// value indicating offset to the right and a negative value indicating offset to the left.
+ /// The second value shall be the vertical offset perpendicular to the annotation line,
+ /// with a positive value indicating a shift up and a negative value indicating a shift down.
+ /// Default value: [0, 0] (no offset from normal positioning)
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string CO = "/CO";
+
+ // ReSharper restore InconsistentNaming
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLinkAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLinkAnnotation.cs
index 38ec5c04..40621011 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLinkAnnotation.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLinkAnnotation.cs
@@ -6,102 +6,158 @@
using PdfSharp.Pdf.IO;
using PdfSharp.Pdf.Internal;
+// v7.0.0 TODO review creator functions
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Annotations
+{
+ // Just a hack to make MigraDoc work with this code.
+ enum PdfLinkAnnotationTypes
+ {
+ None,
+ Document,
+ NamedDestination,
+ Web,
+ File // TODO: un-nest
+ }
+}
+
namespace PdfSharp.Pdf.Annotations
{
///
- /// Represents a link annotation.
+ /// Represents a PDF link annotation.
///
public sealed class PdfLinkAnnotation : PdfAnnotation
{
- // Just a hack to make MigraDoc work with this code.
- enum LinkType
- {
- None, Document, NamedDestination, Web, File
- }
+ // Reference 2.0: 12.5.6.5 Link annotations / Page 482
+
+ [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")]
+ public PdfLinkAnnotation()
+ => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter.");
///
/// Initializes a new instance of the class.
///
- public PdfLinkAnnotation()
+ public PdfLinkAnnotation(PdfDocument document)
+ : base(document)
{
- _linkType = LinkType.None;
- Elements.SetName(PdfAnnotation.Keys.Subtype, "/Link");
+ Initialize();
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
- public PdfLinkAnnotation(PdfDocument document)
- : base(document)
+ internal PdfLinkAnnotation(PdfDictionary dict)
+ : base(dict)
+ {
+ Initialize();
+ }
+
+ void Initialize()
{
- _linkType = LinkType.None;
- Elements.SetName(PdfAnnotation.Keys.Subtype, "/Link");
+ _linkType = PdfLinkAnnotationTypes.None;
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Link);
}
+ const string ObsoleteMessage = "PDFsharp 6.4: Parameter requires a PDF page as first parameter.";
+
+ [Obsolete(ObsoleteMessage)]
+ public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, int destinationPage, XPoint? point = null)
+ => throw new NotImplementedException(ObsoleteMessage);
+
///
/// Creates a link within the current document.
///
+ /// The page the annotation belong to.
/// The link area in default page coordinates.
/// The one-based destination page number.
/// The position in the destination page.
- public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, int destinationPage, XPoint? point = null)
+ public static PdfLinkAnnotation CreateDocumentLink(PdfPage page, PdfRectangle rect, int destinationPage, XPoint? point = null)
{
+ EnsurePageHasDocument(page);
+
if (destinationPage < 1)
throw new ArgumentException("Invalid destination page in call to CreateDocumentLink: page number is one-based and must be 1 or higher.", nameof(destinationPage));
- PdfLinkAnnotation link = new PdfLinkAnnotation();
- link._linkType = LinkType.Document;
- link.Rectangle = rect;
- link._destPage = destinationPage;
- link._point = point;
+ var link = new PdfLinkAnnotation(page.Document)
+ {
+ _linkType = PdfLinkAnnotationTypes.Document,
+ Rectangle = rect,
+ _destPage = destinationPage,
+ _point = point
+ };
+ page.Annotations.Add(link);
return link;
}
int _destPage;
- LinkType _linkType;
+ PdfLinkAnnotationTypes _linkType;
string _url = "";
- private XPoint? _point = null;
+ XPoint? _point = null;
+
+ [Obsolete(ObsoleteMessage)]
+ public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string destinationName)
+ => throw new NotImplementedException(ObsoleteMessage);
///
/// Creates a link within the current document using a named destination.
///
+ /// The page the annotation belong to.
/// The link area in default page coordinates.
/// The named destination’s name.
- public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string destinationName)
+ public static PdfLinkAnnotation CreateDocumentLink(PdfPage page, PdfRectangle rect, string destinationName)
{
- var link = new PdfLinkAnnotation
+ EnsurePageHasDocument(page);
+
+ var link = new PdfLinkAnnotation(page.Document)
{
- _linkType = LinkType.NamedDestination,
+ _linkType = PdfLinkAnnotationTypes.NamedDestination,
Rectangle = rect,
_action = PdfGoToAction.CreateGoToAction(destinationName)
};
+ page.Annotations.Add(link);
return link;
}
- PdfAction _action = default!;
+ PdfAction _action = null!;
+
+ [Obsolete(ObsoleteMessage)]
+ public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string documentPath, string destinationName, bool? newWindow = null)
+ => throw new NotImplementedException(ObsoleteMessage);
///
/// Creates a link to an external PDF document using a named destination.
///
+ /// The page the annotation belong to.
/// The link area in default page coordinates.
/// The path to the target document.
/// The named destination’s name in the target document.
/// True, if the destination document shall be opened in a new window.
/// If not set, the viewer application should behave in accordance with the current user preference.
- public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string documentPath, string destinationName, bool? newWindow = null)
+ public static PdfLinkAnnotation CreateDocumentLink(PdfPage page, PdfRectangle rect, string documentPath, string destinationName, bool? newWindow = null)
{
- var link = new PdfLinkAnnotation
+ EnsurePageHasDocument(page);
+
+ var link = new PdfLinkAnnotation(page.Document)
{
- _linkType = LinkType.NamedDestination,
+ _linkType = PdfLinkAnnotationTypes.NamedDestination,
Rectangle = rect,
_action = PdfRemoteGoToAction.CreateRemoteGoToAction(documentPath, destinationName, newWindow)
};
+ page.Annotations.Add(link);
return link;
}
+ [Obsolete(ObsoleteMessage)]
+ public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, string destinationPath, bool? newWindow = null)
+ => throw new NotImplementedException(ObsoleteMessage);
+
///
/// Creates a link to an embedded document.
///
+ /// The page the annotation belong to.
/// The link area in default page coordinates.
/// The path to the named destination through the embedded documents.
/// The path is separated by '\' and the last segment is the name of the named destination.
@@ -109,18 +165,27 @@ public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string doc
/// ".." references to the parent, other strings refer to a child with this name in the EmbeddedFiles name dictionary.
/// True, if the destination document shall be opened in a new window.
/// If not set, the viewer application should behave in accordance with the current user preference.
- public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, string destinationPath, bool? newWindow = null)
+ public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfPage page, PdfRectangle rect, string destinationPath, bool? newWindow = null)
{
- PdfLinkAnnotation link = new PdfLinkAnnotation();
- link._linkType = LinkType.NamedDestination;
+ EnsurePageHasDocument(page);
+
+ //PdfLinkAnnotation link = new PdfLinkAnnotation(page); // TODO: Dangerous bug. Page binds to PdfDictionary and creates a type transformation.
+ PdfLinkAnnotation link = new PdfLinkAnnotation(page.Document);
+ link._linkType = PdfLinkAnnotationTypes.NamedDestination;
link.Rectangle = rect;
link._action = PdfEmbeddedGoToAction.CreatePdfEmbeddedGoToAction(destinationPath, newWindow);
+ page.Annotations.Add(link);
return link;
}
+ [Obsolete(ObsoleteMessage)]
+ public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, string documentPath, string destinationPath, bool? newWindow = null)
+ => throw new NotImplementedException(ObsoleteMessage);
+
///
/// Creates a link to an embedded document in another document.
///
+ /// The page the annotation belong to.
/// The link area in default page coordinates.
/// The path to the target document.
/// The path to the named destination through the embedded documents in the target document.
@@ -129,41 +194,64 @@ public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, st
/// Each segment name refers to a child with this name in the EmbeddedFiles name dictionary.
/// True, if the destination document shall be opened in a new window.
/// If not set, the viewer application should behave in accordance with the current user preference.
- public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, string documentPath, string destinationPath, bool? newWindow = null)
+ public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfPage page, PdfRectangle rect, string documentPath, string destinationPath, bool? newWindow = null)
{
- PdfLinkAnnotation link = new PdfLinkAnnotation();
- link._linkType = LinkType.NamedDestination;
+ EnsurePageHasDocument(page);
+
+ PdfLinkAnnotation link = new PdfLinkAnnotation(page.Document);
+ link._linkType = PdfLinkAnnotationTypes.NamedDestination;
link.Rectangle = rect;
link._action = PdfEmbeddedGoToAction.CreatePdfEmbeddedGoToAction(documentPath, destinationPath, newWindow);
+ page.Annotations.Add(link);
return link;
}
+ [Obsolete(ObsoleteMessage)]
+ public static PdfLinkAnnotation CreateWebLink(PdfRectangle rect, string url)
+ => throw new NotImplementedException(ObsoleteMessage);
+
///
/// Creates a link to the web.
///
- public static PdfLinkAnnotation CreateWebLink(PdfRectangle rect, string url)
+ /// The page the annotation belong to.
+ /// The rectangle using World coordinates.
+ /// The destination URL.
+ public static PdfLinkAnnotation CreateWebLink(PdfPage page, PdfRectangle rect, string url)
{
- var link = new PdfLinkAnnotation
+ EnsurePageHasDocument(page);
+
+ var link = new PdfLinkAnnotation(page.Document)
{
- _linkType = PdfLinkAnnotation.LinkType.Web,
+ _linkType = PdfLinkAnnotationTypes.Web,
Rectangle = rect,
_url = url
};
+ page.Annotations.Add(link);
return link;
}
+ [Obsolete("PDFsharp 6.4: Requires a PDF page as first parameter.")]
+ public static PdfLinkAnnotation CreateFileLink(PdfRectangle rect, string fileName)
+ => throw new NotImplementedException("");
+
///
/// Creates a link to a file.
///
- public static PdfLinkAnnotation CreateFileLink(PdfRectangle rect, string fileName)
+ /// The page the annotation belong to.
+ /// The rectangle using World coordinates.
+ /// The destination file.
+ public static PdfLinkAnnotation CreateFileLink(PdfPage page, PdfRectangle rect, string fileName)
{
- var link = new PdfLinkAnnotation
+ EnsurePageHasDocument(page);
+
+ var link = new PdfLinkAnnotation(page.Document)
{
- _linkType = LinkType.File,
+ _linkType = PdfLinkAnnotationTypes.File,
// TODO_OLD: Adjust bleed box here (if possible)
Rectangle = rect,
_url = fileName
};
+ page.Annotations.Add(link);
return link;
}
@@ -177,55 +265,52 @@ internal override void WriteObject(PdfWriter writer)
// Older Adobe Reader versions uses a border width of 0 as default value if neither Border nor BS are present.
// But the PDF Reference specifies:
- // "If neither the Border nor the BS entry is present, the border is drawn as a solid line with a width of 1 point."
+ // “If neither the Border nor the BS entry is present, the border is drawn as a solid line with a width of 1 point.”
// After this issue was fixed in newer Reader versions older PDFsharp created documents show an ugly solid border.
// The following hack fixes this by specifying a 0 width border.
- if (Elements[PdfAnnotation.Keys.BS] == null)
- Elements[PdfAnnotation.Keys.BS] = new PdfLiteral("<>");
+ //if (Elements[Keys.BS] == null) // TODO #US373 Just a null check.
+ if (!Elements.HasValue(Keys.BS)) // #US373
+ Elements[Keys.BS] = new PdfLiteral("<>");
// May be superfluous. See comment above.
- if (Elements[PdfAnnotation.Keys.Border] == null)
+ //if (Elements[PdfAnnotation.Keys.Border] == null) // TODO #US373 Just a null check.
+ if (!Elements.HasValue(PdfAnnotation.Keys.Border)) // #US373
Elements[PdfAnnotation.Keys.Border] = new PdfLiteral("[0 0 0]");
switch (_linkType)
{
- case LinkType.None:
+ case PdfLinkAnnotationTypes.None:
break;
- case LinkType.Document:
+ case PdfLinkAnnotationTypes.Document:
// destIndex > Owner.PageCount can happen when rendering pages using PDFsharp directly.
int destIndex = _destPage;
if (destIndex > Owner.PageCount)
destIndex = Owner.PageCount;
destIndex--;
dest = Owner.Pages[destIndex];
- ////pdf.AppendFormat("/Dest[{0} 0 R/XYZ null null 0]\n", dest.ObjectID);
- //Elements[Keys.Dest] = new PdfLiteral("[{0} 0 R/XYZ null null 0]", dest.ObjectNumber);
+ // TODO: Use format strings for double.
if (_point.HasValue)
{
- Elements[Keys.Dest] = new PdfLiteral(Invariant($"[{dest.ObjectNumber} 0 R /XYZ {_point.Value.X} {_point.Value.Y} 0]") /*, dest.ObjectNumber, _point.Value.X, _point.Value.Y*/);
+ Elements[Keys.Dest] = new PdfLiteral(Invariant($"[{dest.ObjectNumber} 0 R /XYZ {_point.Value.X} {_point.Value.Y} 0]"));
}
else
{
- Elements[Keys.Dest] = new PdfLiteral(Invariant($"[{dest.ObjectNumber} 0 R /XYZ null null 0]") /*, dest.ObjectNumber*/);
+ Elements[Keys.Dest] = new PdfLiteral(Invariant($"[{dest.ObjectNumber} 0 R /XYZ null null 0]"));
}
break;
- case LinkType.NamedDestination:
- Elements[PdfAnnotation.Keys.A] = _action;
+ case PdfLinkAnnotationTypes.NamedDestination:
+ Elements[Keys.A] = _action;
break;
- case LinkType.Web:
- //pdf.AppendFormat("/A<>\n", PdfEncoders.EncodeAsLiteral(url));
- Elements[PdfAnnotation.Keys.A] = new PdfLiteral("<>", //PdfEncoders.EncodeAsLiteral(url));
+ case PdfLinkAnnotationTypes.Web:
+ Elements[Keys.A] = new PdfLiteral("<>",
PdfEncoders.ToStringLiteral(_url, PdfStringEncoding.WinAnsiEncoding, writer.EffectiveSecurityHandler));
break;
- case LinkType.File:
- //pdf.AppendFormat("/A<> >>\n",
- // PdfEncoders.EncodeAsLiteral(url));
- Elements[PdfAnnotation.Keys.A] = new PdfLiteral("<> >>",
- //PdfEncoders.EncodeAsLiteral(url));
+ case PdfLinkAnnotationTypes.File:
+ Elements[Keys.A] = new PdfLiteral("<>>>",
PdfEncoders.ToStringLiteral(_url, PdfStringEncoding.WinAnsiEncoding, writer.EffectiveSecurityHandler));
break;
}
@@ -235,49 +320,77 @@ internal override void WriteObject(PdfWriter writer)
///
/// Predefined keys of this dictionary.
///
- internal new class Keys : PdfAnnotation.Keys
+ public new class Keys : PdfAnnotation.Keys
{
- // ///
- // /// (Required) The type of annotation that this dictionary describes;
- // /// must be Link for a link annotation.
- // ///
- // inherited from base class
+ // Reference 2.0: Table 176 — Additional entries specific to a link annotation / Page 483
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional; PDF 1.1) An action that shall be performed when the link annotation is activated.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Required)]
+ public const string A = "/A";
///
- /// (Optional; not permitted if an A entry is present) A destination to be displayed
- /// when the annotation is activated.
+ /// (Optional; not permitted if an A entry is present) A destination that shall be displayed
+ /// when the annotation is activated (12.3.2, "Destinations").
///
[KeyInfo(KeyType.ArrayOrNameOrString | KeyType.Optional)]
public const string Dest = "/Dest";
///
- /// (Optional; PDF 1.2) The annotation’s highlighting mode, the visual effect to be
- /// used when the mouse button is pressed or held down inside its active area:
- /// N (None) No highlighting.
- /// I (Invert) Invert the contents of the annotation rectangle.
- /// O (Outline) Invert the annotation’s border.
- /// P (Push) Display the annotation as if it were being pushed below the surface of the page.
+ /// (Optional; PDF 1.2) The annotation’s highlighting mode, the visual effect that shall be
+ /// used when the mouse button is pressed or held down inside its active area:
+ /// N (None) No highlighting.
+ /// I (Invert) Invert the contents of the annotation rectangle.
+ /// O (Outline) Invert the annotation’s border.
+ /// P (Push) Display the annotation as if it were being pushed below the surface of the page.
/// Default value: I.
- /// Note: In PDF 1.1, highlighting is always done by inverting colors inside the annotation rectangle.
///
[KeyInfo("1.2", KeyType.Name | KeyType.Optional)]
public const string H = "/H";
///
- /// (Optional; PDF 1.3) A URI action formerly associated with this annotation. When Web
- /// Capture changes and annotation from a URI to a go-to action, it uses this entry to save
- /// the data from the original URI action so that it can be changed back in case the target page for
- /// the go-to action is subsequently deleted.
+ /// (Optional; PDF 1.3) A URI action (see 12.6.4.8, "URI actions") formerly associated with this
+ /// annotation. When a PDF processor changes an annotation from a URI to a go-to action, it may
+ /// use this entry to save the data from the original URI action so that it can be changed back
+ /// in case the target page for the go-to action is subsequently deleted.
///
[KeyInfo("1.3", KeyType.Dictionary | KeyType.Optional)]
public const string PA = "/PA";
- // QuadPoints
+ ///
+ /// (Optional; PDF 1.6) An array of 8×𝑛 numbers specifying the coordinates of n quadrilaterals
+ /// in default user space that comprise the region in which the link should be activated. The
+ /// coordinates for each quadrilateral are given in the order:
+ /// 𝑥1 𝑦1 𝑥2 𝑦2 𝑥3 𝑦3 𝑥4 𝑦4
+ /// specifying the four vertices of the quadrilateral in counterclockwise order.For orientation
+ /// purposes, such as when applying an underline border style, the bottom of a quadrilateral is
+ /// the line formed by (x1, y1) and (x2, y2).
+ /// If this entry is not present, or the PDF processor does not recognise it, or if any
+ /// coordinates in the QuadPoints array lie outside the region specified by Rect then the
+ /// activation region for the link annotation shall be defined by its Rect entry.
+ /// NOTE
+ /// The last paragraph above was clarified in this document (2020).
+ ///
+ [KeyInfo("1.6", KeyType.Array | KeyType.Optional)]
+ public const string QuadPoints = "/QuadPoints";
+
+
+ ///
+ /// (Optional; PDF 1.6) A border style dictionary specifying the line width and dash pattern
+ /// that shall be used in drawing the annotation’s border.
+ ///
+ [KeyInfo("1.6", KeyType.Dictionary | KeyType.Optional)]
+ public const string BS = "/BS";
+
+ // ReSharper restore InconsistentNaming
///
/// Gets the KeysMeta for these keys.
///
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMarkupAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMarkupAnnotation.cs
new file mode 100644
index 00000000..ad7d11e9
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMarkupAnnotation.cs
@@ -0,0 +1,166 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Base class of all Markup annotations.
+ ///
+ public abstract class PdfMarkupAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 12.5.6.2 Markup annotations / Page 477
+ [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")]
+ protected PdfMarkupAnnotation()
+ => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter.");
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfMarkupAnnotation(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfMarkupAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Gets or sets the text label to be displayed in the title bar of the annotation’s
+ /// pop-up window when open and active. By convention, this entry identifies
+ /// the user who added the annotation.
+ ///
+ public string Title
+ {
+ get => Elements.GetString(Keys.T, true);
+ set
+ {
+ Elements.SetString(Keys.T, value);
+ Elements.SetDateTime(PdfAnnotation.Keys.M, DateTimeOffset.Now);
+ }
+ }
+
+ ///
+ /// Gets or sets text representing a short description of the subject being
+ /// addressed by the annotation.
+ ///
+ public string Subject
+ {
+ get => Elements.GetString(Keys.Subj, true);
+ set
+ {
+ Elements.SetString(Keys.Subj, value);
+ Elements.SetDateTime(PdfAnnotation.Keys.M, DateTimeOffset.Now);
+ }
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ // Reference 2.0: Table 172 — Additional entries in an annotation dictionary specific to markup annotations / Page 479
+
+ // ReSharper disable InconsistentNaming
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ ///
+ /// (Optional; PDF 1.1) The text label that shall be displayed in the title bar of the
+ /// annotation’s popup window when open and active. This entry shall identify the user
+ /// who added the annotation.
+ ///
+ [KeyInfo("1.1", KeyType.TextString | KeyType.Optional)]
+ public const string T = "/T";
+
+ ///
+ /// (Optional; PDF 1.3) An indirect reference to a pop-up annotation for entering or
+ /// editing the text associated with this annotation.
+ ///
+ [KeyInfo("1.3", KeyType.Dictionary | KeyType.Optional)]
+ public const string Popup = "/Popup";
+
+ ///
+ /// (Optional; PDF 1.5) A rich text string (see Adobe XML Architecture, XML Forms
+ /// Architecture (XFA) Specification, version 3.3) that shall be displayed in the
+ /// popup window when the annotation is opened.
+ ///
+ [KeyInfo("1.5", KeyType.TextStringOrTextStream | KeyType.Optional)]
+ public const string RC = "/RC";
+
+ ///
+ /// (Optional; PDF 1.5) The date and time (7.9.4, "Dates") when the annotation
+ /// was created.
+ ///
+ [KeyInfo("1.5", KeyType.Date | KeyType.Optional)]
+ public const string CreationDate = "/CreationDate";
+
+ ///
+ /// (Required if an RT entry is present, otherwise optional; PDF 1.5) A reference to
+ /// the annotation that this annotation is "in reply to." Both annotations shall be on
+ /// the same page of the document. The relationship between the two annotations shall
+ /// be specified by the RT entry.
+ /// If this entry is present in an FDF file (see 12.7.8, "Forms data format"), its type
+ /// shall not be a dictionary but a text string containing the contents of the NM entry
+ /// of the annotation being replied to, to allow for a situation where the annotation
+ /// being replied to is not in the same FDF file.
+ ///
+ [KeyInfo("1.5", KeyType.Dictionary | KeyType.Optional)]
+ public const string IRT = "/IRT";
+
+ ///
+ /// (Optional; PDF 1.5) Text representing a short description of the subject being
+ /// addressed by the annotation.
+ ///
+ [KeyInfo("1.5", KeyType.TextString | KeyType.Optional)]
+ public const string Subj = "/Subj";
+
+ ///
+ /// (Optional; meaningful only if IRT is present; PDF 1.6) A name specifying the
+ /// relationship (the "reply type") between this annotation and one specified
+ /// by IRT.
+ /// Valid values are:
+ /// R The annotation is considered a reply to the annotation specified by IRT.
+ /// Interactive PDF processors shall not display replies to an annotation
+ /// individually but together in the form of threaded comments.
+ /// Group The annotation shall be grouped with the annotation specified by IRT;
+ /// see the discussion following this Table.
+ /// Default value: R.
+ ///
+ [KeyInfo("1.6", KeyType.Name | KeyType.Optional)]
+ public const string RT = "/RT";
+
+ ///
+ /// (Optional; PDF 1.6) A name describing the intent of the markup annotation.
+ /// Intents allow interactive PDF processors to distinguish between different
+ /// uses and behaviours of a single markup annotation type. If this entry is
+ /// not present or its value is the same as the annotation type, the annotation
+ /// shall have no explicit intent and should behave in a generic manner in an
+ /// interactive PDF processor. Free text annotations ("Table 177 — Additional
+ /// entries specific to a free text annotation"), line annotations
+ /// ("Table 178 — Additional entries specific to a line annotation"),
+ /// polygon annotations ("Table 181 — Additional entries specific to a polygon
+ /// or polyline annotation"), (PDF 1.7) polyline annotations
+ /// ("Table 181 — Additional entries specific to a polygon or polyline
+ /// annotation") and stamp annotations (“Table 184 — Additional entries
+ /// specific to a rubber stamp annotation") have defined intents, whose values
+ /// are enumerated in the corresponding tables.
+ ///
+ [KeyInfo("1.6", KeyType.Name | KeyType.Optional)]
+ public const string IT = "/IT";
+
+ // ReSharper restore InconsistentNaming
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMovieAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMovieAnnotation.cs
new file mode 100644
index 00000000..eabf2104
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMovieAnnotation.cs
@@ -0,0 +1,76 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF movie annotation.
+ ///
+ public sealed class PdfMovieAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 12.5.6.17 Movie annotations / Page 497
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfMovieAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfMovieAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Movie);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) The title of the movie annotation. Movie actions (12.6.4.10, "Movie actions")
+ /// may use this title to reference the movie annotation.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string T = "/T";
+
+ ///
+ /// (Required) A movie dictionary that shall describe the movie’s static characteristics
+ /// (see 13.4, "Movies").
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Required)]
+ public const string Movie = "/Movie";
+
+ ///
+ /// (Optional) A flag or dictionary specifying whether and how to play the movie when the
+ /// annotation is activated. If this value is a dictionary, it shall be a movie activation
+ /// dictionary (see 13.4, "Movies") specifying how to play the movie. If the value is the
+ /// boolean true, the movie shall be played using default activation parameters. If the
+ /// value is false, the movie shall not be played. Default value: true.
+ ///
+ [KeyInfo(KeyType.BooleanOrDictionary | KeyType.Optional)]
+ public const string A = "/A";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPolyAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPolyAnnotation.cs
new file mode 100644
index 00000000..4005fbf6
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPolyAnnotation.cs
@@ -0,0 +1,188 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Base class of PdfPolygonAnnotation and PdfPolyLineAnnotation.
+ ///
+ public abstract class PdfPolyAnnotation : PdfMarkupAnnotation
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfPolyAnnotation(PdfDocument document)
+
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfPolyAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ // Reference 2.0: Table 181 — Additional entries specific to a polygon or polyline annotation / Page 491
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required unless a Path key is present, in which case it shall be ignored) An array
+ /// of numbers specifying the alternating horizontal and vertical coordinates, respectively,
+ /// of each vertex, in default user space.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string Vertices = "/Vertices";
+
+ ///
+ /// (Optional; meaningful only for polyline annotations) An array of two names that shall
+ /// specify the line ending styles. The first and second elements of the array shall specify
+ /// the line ending styles for the endpoints defined, respectively, by the first and last
+ /// pairs of coordinates in the Vertices array. "Table 179 — Line ending styles" shows the
+ /// allowed values. Default value: [/None /None].
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string LE = "/LE";
+
+ ///
+ /// (Optional) A border style dictionary (see "Table 168 — Entries in a border style dictionary")
+ /// specifying the width and dash pattern that shall be used in drawing the line.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string BS = "/BS";
+
+ ///
+ /// (Optional) An array of numbers that shall be in the range 0.0 to 1.0 and shall specify
+ /// the interior color with which to fill the annotation’s line endings
+ /// (see "Table 179 — Line ending styles"). The number of array elements determines the
+ /// colour space in which the colour shall be defined:
+ /// 0 No colour; transparent
+ /// 1 DeviceGray
+ /// 3 DeviceRGB
+ /// 4 DeviceCMYK
+ /// For Polyline annotations, the value of the IC key is used to fill only the line ending.
+ /// However, for Polygon annotations, the value of the IC key is used to fill the entire
+ /// shape, much as the F operator would fill a shape in a content stream.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string IC = "/IC";
+
+ ///
+ /// (Optional; meaningful only for polygon annotations) A border effect dictionary that
+ /// shall describe an effect applied to the border described by the BS entry
+ /// (see "Table 169 — Entries in a border effect dictionary").
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string BE = "/BE";
+
+ ///
+ /// (Optional; PDF 1.6) A name that shall describe the intent of the polygon or polyline
+ /// annotation (see also "Table 172 — Additional entries in an annotation dictionary
+ /// specific to markup annotations").
+ /// The following values shall be valid:
+ /// PolygonCloud The annotation is intended to function as a cloud object.
+ /// PolyLineDimension (PDF 1.7) The polyline annotation is intended to function as a dimension.
+ /// PolygonDimension (PDF 1.7) The polygon annotation is intended to function as a dimension.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string IT = "/IT";
+
+ ///
+ /// (Optional; PDF 1.7) A measure dictionary (see "Table 266 — Entries in a measure dictionary")
+ /// that shall specify the scale and units that apply to the annotation.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string Measure = "/Measure";
+
+ ///
+ /// (Optional; PDF 2.0) An array of n arrays, each supplying the operands for a path building
+ /// operator (m, l or c). If this key is present the Vertices key shall not be present.
+ /// Each of the n arrays shall contain pairs of values specifying the points (x and y values)
+ /// for a path drawing operation. The first array shall be of length 2 and specifies the
+ /// operand of a moveto operator which establishes a current point. Subsequent arrays of
+ /// length 2 specify the operands of lineto operators. Arrays of length 6 specify the operands
+ /// for curveto operators.Each array is processed in sequence to construct the path. The
+ /// current graphics state shall control the path width, dash pattern, etc.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string Path = "/Path";
+
+ // ReSharper restore InconsistentNaming
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+
+ ///
+ /// Represents a PDF polygon annotation.
+ ///
+ public sealed class PdfPolygonAnnotation : PdfPolyAnnotation
+ {
+ // Reference 2.0: 12.5.6.9 Polygon and polyline annotations / Page 491
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfPolygonAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfPolygonAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Polygon);
+ }
+ }
+
+ ///
+ /// Represents a PDF polyline annotation.
+ ///
+ public sealed class PdfPolyLineAnnotation : PdfPolyAnnotation
+ {
+ // Reference 2.0: 12.5.6.9 Polygon and polyline annotations / Page 491
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfPolyLineAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfPolyLineAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.PolyLine);
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPopupAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPopupAnnotation.cs
new file mode 100644
index 00000000..2e1f8338
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPopupAnnotation.cs
@@ -0,0 +1,69 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF popup annotation.
+ ///
+ public sealed class PdfPopupAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 12.5.6.14 Popup annotations / Page 495
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfPopupAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfPopupAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Popup);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional; shall be an indirect reference) The parent annotation with which this popup
+ /// annotation shall be associated. If this entry is present, the parent annotation’s Contents,
+ /// M, C, and T entries (see "Table 170 — Entries in an appearance dictionary") shall override
+ /// those of the popup annotation itself. NOTE See also the Popup entry in "Table 172 — Additional
+ /// entries in an annotation dictionary specific to markup annotations".
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string Parent = "/Parent";
+
+ ///
+ /// (Optional) A flag specifying whether the popup annotation shall initially be displayed open.
+ /// Default value: false (closed).
+ ///
+ [KeyInfo(KeyType.Boolean | KeyType.Optional)]
+ public const string Open = "/Open";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPrinterMarkAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPrinterMarkAnnotation.cs
new file mode 100644
index 00000000..5d1c4939
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPrinterMarkAnnotation.cs
@@ -0,0 +1,59 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF printer’s mark annotation.
+ ///
+ public sealed class PdfPrinterMarkAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 12.5.6.20 Printer’s mark annotations / Page 501
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfPrinterMarkAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfPrinterMarkAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.PrinterMark);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) An arbitrary name identifying the type of printer’s mark, such as ColorBar
+ /// or RegistrationTarget.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string MN = "/MN";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfProjectionAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfProjectionAnnotation.cs
new file mode 100644
index 00000000..3222983f
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfProjectionAnnotation.cs
@@ -0,0 +1,37 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF projection annotation.
+ ///
+ public sealed class PdfProjectionAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.24 Projection annotations / Page 505
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfProjectionAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfProjectionAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Projection);
+ }
+
+ // This annotation has no special keys of its own.
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRedactAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRedactAnnotation.cs
new file mode 100644
index 00000000..29627d70
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRedactAnnotation.cs
@@ -0,0 +1,119 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF redact annotation.
+ ///
+ public sealed class PdfRedactAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.23 Redaction annotations / Page 504
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfRedactAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfRedactAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Redact);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) An array of 8 x n numbers specifying the coordinates of n quadrilaterals
+ /// in default user space, as described in "Table 182 — Additional entries specific to
+ /// text markup annotations" for text markup annotations. If present, these quadrilaterals
+ /// denote the content region that is intended to be removed. If this entry is not present,
+ /// the Rect entry denotes the content region that is intended to be removed.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string QuadPoints = "/QuadPoints";
+
+ ///
+ /// (Optional) An array of three numbers in the range 0.0 to 1.0 specifying the components,
+ /// in the DeviceRGB colour space, of the interior colour with which to fill the redacted
+ /// region after the affected content has been removed. If this entry is absent, the interior
+ /// of the redaction region is left transparent.
+ /// This entry is ignored if the RO entry is present.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string IC = "/IC";
+
+ ///
+ /// (Optional) A form XObject specifying the overlay appearance for this redaction annotation.
+ /// After this redaction is applied and the affected content has been removed, the overlay
+ /// appearance should be drawn such that its origin lines up with the lower-left corner
+ /// of the annotation rectangle. This form XObject is not necessarily related to other
+ /// annotation appearances, and may or may not be present in the AP dictionary. This entry
+ /// takes precedence over the IC, OverlayText, DA, and Q entries.
+ ///
+ [KeyInfo(KeyType.Stream | KeyType.Optional)]
+ public const string RO = "/RO";
+
+ ///
+ /// (Optional) A text string specifying the overlay text that should be drawn over the redacted
+ /// region after the affected content has been removed. This entry is ignored if the RO entry
+ /// is present.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string OverlayText = "/OverlayText";
+
+ ///
+ /// (Optional) If true, then the text specified by OverlayText should be repeated to fill
+ /// the redacted region after the affected content has been removed. This entry is ignored
+ /// if the RO entry is present. Default value: false.
+ ///
+ [KeyInfo(KeyType.Boolean | KeyType.Optional)]
+ public const string Repeat = "/Repeat";
+
+ ///
+ /// (Required if OverlayText is present, ignored otherwise) The appearance string that shall
+ /// be used in formatting the overlay text when it is drawn after the affected content has
+ /// been removed (see 12.7.4.3, "Variable text"). This entry is ignored if the RO entry
+ /// is present.
+ ///
+ [KeyInfo(KeyType.ByteString | KeyType.Optional)]
+ public const string DA = "/DA";
+
+ ///
+ /// (Optional) A code specifying the form of quadding (justification) that shall be used
+ /// in laying out the overlay text:
+ /// 0 Left-justified
+ /// 1 Centred
+ /// 2 Right-justified This entry is ignored if the RO entry is present.
+ /// Default value: 0 (left-justified).
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string Q = "/Q";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRichMediaAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRichMediaAnnotation.cs
new file mode 100644
index 00000000..c4fa7d85
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRichMediaAnnotation.cs
@@ -0,0 +1,71 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF RichMedia annotation.
+ ///
+ public sealed class PdfRichMediaAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 13.7.2 RichMedia annotations / Page 700
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfRichMediaAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfRichMediaAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.RichMedia);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required; PDF 2.0) A RichMediaContent dictionary that stores the rich media artwork
+ /// and information as to how it should be configured and viewed. See "Table 341 — Entries
+ /// in a RichMediaContent dictionary".
+ ///
+ [KeyInfo("2.0", KeyType.Dictionary | KeyType.Required)]
+ public const string RichMediaContent = "/RichMediaContent";
+
+ ///
+ /// (Optional; PDF 2.0) A RichMediaSettings dictionary that stores conditions and responses
+ /// that determine when the annotation should be activated and deactivated by an interactive
+ /// PDF processor and the initial state of artwork in those states. See "Table 334 — Entries
+ /// in a RichMediaSettings dictionary". Default value: If no RichMediaSettings dictionary
+ /// is present, the first configuration is loaded.
+ ///
+ [KeyInfo("2.0", KeyType.Dictionary | KeyType.Required)]
+ public const string RichMediaSettings = "/RichMediaSettings";
+
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRubberStampAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRubberStampAnnotation.cs
deleted file mode 100644
index 9550f9e7..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRubberStampAnnotation.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using System;
-using PdfSharp.Drawing;
-
-namespace PdfSharp.Pdf.Annotations
-{
- ///
- /// Represents a rubber stamp annotation.
- ///
- public sealed class PdfRubberStampAnnotation : PdfAnnotation
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public PdfRubberStampAnnotation()
- {
- Initialize();
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The document.
- public PdfRubberStampAnnotation(PdfDocument document)
- : base(document)
- {
- Initialize();
- }
-
- void Initialize()
- {
- Elements.SetName(Keys.Subtype, "/Stamp");
- Color = XColors.Yellow;
- }
-
- ///
- /// Gets or sets an icon to be used in displaying the annotation.
- ///
- public PdfRubberStampAnnotationIcon Icon
- {
- get
- {
- string value = Elements.GetName(Keys.Name);
- if (value == "")
- return PdfRubberStampAnnotationIcon.NoIcon;
- value = value.Substring(1);
- if (!Enum.IsDefined(typeof(PdfRubberStampAnnotationIcon), value))
- return PdfRubberStampAnnotationIcon.NoIcon;
- return (PdfRubberStampAnnotationIcon)Enum.Parse(typeof(PdfRubberStampAnnotationIcon), value, false);
- }
- set
- {
- if (Enum.IsDefined(typeof(PdfRubberStampAnnotationIcon), value) &&
- PdfRubberStampAnnotationIcon.NoIcon != value)
- {
- Elements.SetName(Keys.Name, "/" + value.ToString());
- }
- else
- Elements.Remove(Keys.Name);
- }
- }
-
- ///
- /// Predefined keys of this dictionary.
- ///
- internal new class Keys : PdfAnnotation.Keys
- {
- ///
- /// (Optional) The name of an icon to be used in displaying the annotation. Viewer
- /// applications should provide predefined icon appearances for at least the following
- /// standard names:
- /// Approved
- /// AsIs
- /// Confidential
- /// Departmental
- /// Draft
- /// Experimental
- /// Expired
- /// Final
- /// ForComment
- /// ForPublicRelease
- /// NotApproved
- /// NotForPublicRelease
- /// Sold
- /// TopSecret
- ///
- [KeyInfo(KeyType.Name | KeyType.Optional)]
- public const string Name = "/Name";
-
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
- static DictionaryMeta? _meta;
- }
-
- ///
- /// Gets the KeysMeta of this dictionary type.
- ///
- internal override DictionaryMeta Meta => Keys.Meta;
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfScreenAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfScreenAnnotation.cs
new file mode 100644
index 00000000..fd134331
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfScreenAnnotation.cs
@@ -0,0 +1,80 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF screen annotation.
+ ///
+ public sealed class PdfScreenAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 12.5.6.18 Screen annotations / Page 497
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfScreenAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfScreenAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Screen);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) The title of the screen annotation.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string T = "/T";
+
+ ///
+ /// (Optional) An appearance characteristics dictionary (see "Table 192 — Entries in an
+ /// appearance characteristics dictionary"). The I entry of this dictionary provides the
+ /// icon used in generating the appearance referred to by the screen annotation’s AP entry.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string MK = "/MK";
+
+ ///
+ /// (Optional; PDF 1.1) An action that shall be performed when the annotation is activated
+ /// (see 12.6, "Actions").
+ ///
+ [KeyInfo("1.1", KeyType.Dictionary | KeyType.Optional)]
+ public const string A = "/A";
+
+ ///
+ /// (Optional; PDF 1.2) An additional-actions dictionary defining the screen annotation’s
+ /// behaviour in response to various trigger events (see 12.6.3, "Trigger events").
+ ///
+ [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)]
+ public const string AA = "/AA";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfShapeAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfShapeAnnotation.cs
new file mode 100644
index 00000000..48a8f143
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfShapeAnnotation.cs
@@ -0,0 +1,193 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Drawing;
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Base class of PdfSquareAnnotation and PdfCircleAnnotation.
+ ///
+ public abstract class PdfShapeAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.8 Square and circle annotations / Page 489
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfShapeAnnotation(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfShapeAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ //public XColor FillColor
+ //{
+ // get
+ // {
+ // return XColor.Empty;
+ // }
+ // set
+ // {
+ // }
+ //}
+
+ internal static PdfShapeAnnotation Initialize(PdfShapeAnnotation annot, PdfRectangle rect, XColor color, XColor fillColor)
+ {
+ annot.Page!.Annotations.Add(annot);
+
+ annot.PageRectangle = new XRect(rect.Location, rect.Size);
+ if (!color.IsEmpty)
+ annot.Color = color;
+
+ // Interior color.
+ if (!fillColor.IsEmpty)
+ {
+ // IMPROVE: Write helper for this.
+ var array = new PdfArray(annot.Page.Document);
+ array.Elements.Add(new PdfReal(fillColor.R / 255.0));
+ array.Elements.Add(new PdfReal(fillColor.G / 255.0));
+ array.Elements.Add(new PdfReal(fillColor.B / 255.0));
+ annot.Elements[PdfShapeAnnotation.Keys.IC] = array;
+ }
+
+ return annot;
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ // Reference 2.0: Table 180 — Additional entries specific to a square or circle annotation / Page 490
+
+ // ReSharper disable InconsistentNaming
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ ///
+ /// (Optional) A border style dictionary specifying the line width and dash pattern that
+ /// shall be used in drawing the rectangle or ellipse.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string BS = "/BS";
+
+ ///
+ /// (Optional; PDF 1.4) An array of numbers that shall be in the range 0.0 to 1.0 and shall
+ /// specify the interior colour with which to fill the annotation’s rectangle or ellipse.
+ /// The number of array elements determines the colour space in which the colour shall be
+ /// defined:
+ /// 0 No colour; transparent
+ /// 1 DeviceGray
+ /// 3 DeviceRGB
+ /// 4 DeviceCMYK
+ ///
+ [KeyInfo("1.4", KeyType.Array | KeyType.Optional)]
+ public const string IC = "/IC";
+
+ ///
+ /// (Optional; PDF 1.5) A border effect dictionary describing an effect applied to the
+ /// border described by the BS entry.
+ ///
+ [KeyInfo("1.5", KeyType.Dictionary | KeyType.Optional)]
+ public const string BE = "/BE";
+
+ ///
+ /// (Optional; PDF 1.5) A set of four numbers that shall describe the numerical differences
+ /// between two rectangles: the Rect entry of the annotation and the actual boundaries of
+ /// the underlying square or circle. Such a difference may occur in situations where a border
+ /// effect (described by BE) causes the size of the Rect to increase beyond that of the
+ /// square or circle.
+ /// The four numbers shall correspond to the differences in default user space between the
+ /// left, top, right, and bottom coordinates of Rect and those of the square or circle,
+ /// respectively. Each value shall be greater than or equal to 0. The sum of the top and
+ /// bottom differences shall be less than the height of Rect, and the sum of the left and
+ /// right differences shall be less than the width of Rect.
+ ///
+ [KeyInfo("1.5", KeyType.Rectangle | KeyType.Optional)]
+ public const string RD = "/RD";
+
+ // ReSharper restore InconsistentNaming
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+
+ ///
+ /// Represents a PDF square annotation.
+ ///
+ public sealed class PdfSquareAnnotation : PdfShapeAnnotation
+ {
+ // Reference 2.0: 12.5.6.8 Square and circle annotations / Page 489
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfSquareAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Square);
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfSquareAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ public static PdfSquareAnnotation Create(PdfPage page, PdfRectangle rect, XColor color, XColor fillColor)
+ {
+ var annot = new PdfSquareAnnotation(page.Document);
+ page.Annotations.Add(annot);
+ Initialize(annot, rect, color, fillColor);
+ return annot;
+ }
+ }
+
+ ///
+ /// Represents a PDF circle annotation.
+ ///
+ public sealed class PdfCircleAnnotation : PdfShapeAnnotation
+ {
+ // Reference 2.0: 12.5.6.8 Square and circle annotations / Page 489
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfCircleAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Circle);
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfCircleAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ public static PdfCircleAnnotation Create(PdfPage page, PdfRectangle rect, XColor color, XColor fillColor)
+ {
+ var annot = new PdfCircleAnnotation(page.Document);
+ page.Annotations.Add(annot);
+ Initialize(annot, rect, color, fillColor);
+ return annot;
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfSoundAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfSoundAnnotation.cs
new file mode 100644
index 00000000..d63c8a2f
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfSoundAnnotation.cs
@@ -0,0 +1,68 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF sound annotation.
+ ///
+ public sealed class PdfSoundAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.16 Sound annotations / Page 496
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfSoundAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfSoundAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Sound);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required) A sound object defining the sound that shall be played when the annotation
+ /// is activated (see 13.3, "Sounds").
+ ///
+ [KeyInfo(KeyType.Stream | KeyType.Required)]
+ public const string Sound = "/Sound";
+
+ ///
+ /// (Optional) The name of an icon that shall be used in displaying the annotation.
+ /// PDF writers should include this entry and PDF readers should provide predefined icon
+ /// appearances for at least the standard names Speaker and Mic. Additional names may be
+ /// supported as well. Default value: Speaker.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Name = "/Name";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfStampAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfStampAnnotation.cs
new file mode 100644
index 00000000..52ccc200
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfStampAnnotation.cs
@@ -0,0 +1,127 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Drawing;
+
+// v7.0.0 TODO review creator functions
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF rubber stamp annotation.
+ ///
+ public sealed class PdfStampAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.12 Rubber stamp annotations / Page 494
+
+ [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")]
+ public PdfStampAnnotation()
+ => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter.");
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfStampAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfStampAnnotation(PdfDictionary dict)
+ : base(dict)
+ {
+ Initialize();
+ }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, "/Stamp");
+ Color = XColors.Yellow;
+ }
+
+ ///
+ /// Gets or sets an icon to be used in displaying the annotation.
+ ///
+ public PdfStampAnnotationIcons Icon
+ {
+ get
+ {
+ string value = Elements.GetName(Keys.Name);
+ if (value == "")
+ return PdfStampAnnotationIcons.NoIcon;
+ value = value.Substring(1);
+ if (!Enum.IsDefined(typeof(PdfStampAnnotationIcons), value))
+ return PdfStampAnnotationIcons.NoIcon;
+ return (PdfStampAnnotationIcons)Enum.Parse(typeof(PdfStampAnnotationIcons), value, false);
+ }
+ set
+ {
+ if (Enum.IsDefined(typeof(PdfStampAnnotationIcons), value) &&
+ PdfStampAnnotationIcons.NoIcon != value)
+ {
+ Elements.SetName(Keys.Name, "/" + value.ToString());
+ }
+ else
+ Elements.Remove(Keys.Name);
+ }
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ internal new class Keys : PdfAnnotation.Keys
+ {
+ // Reference 2.0: Table 184 — Additional entries specific to a rubber stamp annotation / Page 494
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) The name of an icon that shall be used in displaying the annotation.
+ /// PDF writers should include this entry and PDF readers should provide predefined icon
+ /// appearances for at least the following standard names:
+ /// Approved, Experimental, NotApproved, AsIs, Expired, NotForPublicRelease, Confidential,
+ /// Final, Sold, Departmental, ForComment, TopSecret, Draft, ForPublicRelease
+ /// Additional names may be supported as well.
+ /// Default value: Draft.
+ /// If the IT key is present and its value is not Stamp, this Name key shall not be present.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Name = "/Name";
+
+ ///
+ /// (Optional; PDF 2.0) A name that shall describe the intent of the stamp. The following
+ /// values shall be valid:
+ /// StampSnapshot The appearance of this annotation has been taken from preexisting PDF content.
+ /// StampImage The appearance of this annotation is an Image.
+ /// Stamp The appearance of this annotation is a rubber stamp.
+ /// Default value: Stamp
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string IT = "/IT";
+
+ // ReSharper restore InconsistentNaming
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+
+ ///
+ /// Use PdfStampAnnotation.
+ ///
+ [Obsolete("Use PdfStampAnnotation.")]
+ public class PdfRubberStampAnnotation
+ { }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextAnnotation.cs
index bb11d8d2..9ad370d6 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextAnnotation.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextAnnotation.cs
@@ -1,28 +1,38 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
+// v7.0.0 TODO review
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Annotations
{
///
- /// Represents a text annotation.
+ /// Represents a PDF text annotation.
///
- public sealed class PdfTextAnnotation : PdfAnnotation
+ public sealed class PdfTextAnnotation : PdfMarkupAnnotation
{
+ // Reference 2.0: 12.5.6.4 Text annotations / Page 482
+
+ [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")]
+ public PdfTextAnnotation()
+ => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter.");
+
///
/// Initializes a new instance of the class.
///
- public PdfTextAnnotation()
+ public PdfTextAnnotation(PdfDocument document)
+ : base(document)
{
Initialize();
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
- public PdfTextAnnotation(PdfDocument document)
- : base(document)
+ internal PdfTextAnnotation(PdfDictionary dict)
+ : base(dict)
{
Initialize();
}
@@ -31,7 +41,7 @@ void Initialize()
{
Elements.SetName(Keys.Subtype, "/Text");
// By default make a yellow comment.
- Icon = PdfTextAnnotationIcon.Comment;
+ Icon = PdfTextAnnotationIcons.Comment;
//Color = XColors.Yellow;
}
@@ -56,22 +66,22 @@ public bool Open
///
/// Gets or sets an icon to be used in displaying the annotation.
///
- public PdfTextAnnotationIcon Icon
+ public PdfTextAnnotationIcons Icon
{
get
{
string value = Elements.GetName(Keys.Name);
- if (value == "")
- return PdfTextAnnotationIcon.NoIcon;
- value = value.Substring(1);
- if (!Enum.IsDefined(typeof(PdfTextAnnotationIcon), value))
- return PdfTextAnnotationIcon.NoIcon;
- return (PdfTextAnnotationIcon)Enum.Parse(typeof(PdfTextAnnotationIcon), value, false);
+ if (value == "" || value == "/")
+ return PdfTextAnnotationIcons.NoIcon;
+ value = value[1..];
+ if (!Enum.IsDefined(typeof(PdfTextAnnotationIcons), value))
+ return PdfTextAnnotationIcons.NoIcon;
+ return (PdfTextAnnotationIcons)Enum.Parse(typeof(PdfTextAnnotationIcons), value, false);
}
set
{
- if (Enum.IsDefined(typeof(PdfTextAnnotationIcon), value) &&
- PdfTextAnnotationIcon.NoIcon != value)
+ if (Enum.IsDefined(typeof(PdfTextAnnotationIcons), value) &&
+ PdfTextAnnotationIcons.NoIcon != value)
{
Elements.SetName(Keys.Name, "/" + value.ToString());
}
@@ -85,32 +95,41 @@ public PdfTextAnnotationIcon Icon
///
internal new class Keys : PdfAnnotation.Keys
{
+ // Reference 2.0: Table 175 — Additional entries specific to a text annotation / Page 482
+
///
- /// (Optional) A flag specifying whether the annotation should initially be displayed open.
+ /// (Optional) A flag specifying whether the annotation shall initially be displayed open.
/// Default value: false (closed).
///
[KeyInfo(KeyType.Boolean | KeyType.Optional)]
public const string Open = "/Open";
///
- /// (Optional) The name of an icon to be used in displaying the annotation. Viewer
- /// applications should provide predefined icon appearances for at least the following
- /// standard names:
- /// Comment
- /// Help
- /// Insert
- /// Key
- /// NewParagraph
- /// Note
- /// Paragraph
+ /// (Optional) The name of an icon that shall be used in displaying the annotation.
+ /// Interactive PDF processors shall provide predefined icon appearances for at least
+ /// the following standard names:
+ /// Comment, Key, Note, Help, NewParagraph, Paragraph, Insert
+ /// Additional names may be supported as well.
+ /// Default value: Note.
///
[KeyInfo(KeyType.Name | KeyType.Optional)]
public const string Name = "/Name";
- //State
- //StateModel
+ ///
+ /// (Optional; PDF 1.5) The state to which the original annotation shall be set.
+ /// Default: Unmarked if StateModel is Marked; None if StateModel is Review.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string State = "/State";
+
+ ///
+ /// (Required if State is present, otherwise optional; PDF 1.5)
+ /// The state model corresponding to State.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string StateModel = "/StateModel";
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextMarkupAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextMarkupAnnotation.cs
new file mode 100644
index 00000000..32a6d9cd
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextMarkupAnnotation.cs
@@ -0,0 +1,192 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Base class of all Text Markup annotations.
+ ///
+ public abstract class PdfTextMarkupAnnotation : PdfMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492
+ [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")]
+ protected PdfTextMarkupAnnotation()
+ => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter.");
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfTextMarkupAnnotation(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfTextMarkupAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfMarkupAnnotation.Keys
+ {
+ // Reference 2.0: Table 182 — Additional entries specific to text markup annotations / Page 492
+
+ // ReSharper disable InconsistentNaming
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ ///
+ /// (Required) An array of 8×𝑛 numbers specifying the coordinates of n quadrilaterals in
+ /// default user space. Each quadrilateral shall encompasses a word or group of contiguous
+ /// words in the text underlying the annotation. The coordinates for each quadrilateral
+ /// shall be given in the order:
+ /// 𝑥1 𝑦1 𝑥2 𝑦2 𝑥3 𝑦3 𝑥4 𝑦4
+ /// specifying the quadrilateral’s four vertices in counterclockwise order
+ /// (see "Figure 84 — QuadPoints specification"). The text shall be oriented with respect
+ /// to the edge connecting points(x1, y1) and(x2, y2).
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Required)]
+ public const string QuadPoints = "/QuadPoints";
+
+
+ // ReSharper restore InconsistentNaming
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+
+ ///
+ /// Represents a PDF highlight annotation.
+ ///
+ public sealed class PdfHighlightAnnotation : PdfTextMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfHighlightAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfHighlightAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Highlight);
+ }
+ }
+
+ ///
+ /// Represents a PDF underline annotation.
+ ///
+ public sealed class PdfUnderlineAnnotation : PdfTextMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfUnderlineAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfUnderlineAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Underline);
+ }
+ }
+
+ ///
+ /// Represents a PDF squiggly annotation.
+ ///
+ public sealed class PdfSquigglyAnnotation : PdfTextMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfSquigglyAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfSquigglyAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Squiggly);
+ }
+ }
+
+ ///
+ /// Represents a PDF strikeout annotation.
+ ///
+ public sealed class PdfStrikeOutAnnotation : PdfTextMarkupAnnotation
+ {
+ // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfStrikeOutAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfStrikeOutAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.StrikeOut);
+ }
+
+ /////
+ ///// Gets the KeysMeta of this dictionary type.
+ /////
+ //internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTrapNetAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTrapNetAnnotation.cs
new file mode 100644
index 00000000..e58e2897
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTrapNetAnnotation.cs
@@ -0,0 +1,95 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF trap network annotation.
+ ///
+ public sealed class PdfTrapNetAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 12.5.6.21 Trap network annotations / Page 501
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfTrapNetAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfTrapNetAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.TrapNet);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfAnnotation.Keys
+ {
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required if Version and AnnotStates are absent; shall be absent if Version and AnnotStates
+ /// are present; PDF 1.4) The date and time (see 7.9.4, "Dates") when the trap network was
+ /// most recently modified.
+ ///
+ [KeyInfo(KeyType.Date | KeyType.Required)]
+ public const string LastModified = "/LastModified";
+
+ ///
+ /// (Required if AnnotStates is present; shall be absent if LastModified is present) An unordered
+ /// array of all objects present in the page description at the time the trap networks were
+ /// generated and that, if changed, could affect the appearance of the page. If present,
+ /// the array shall include the following objects:
+ /// • All content streams identified in the page object’s Contents entry(see 7.7.3.3,
+ /// "Page objects")
+ /// • All resource objects(other than procedure sets) in the page’s resource dictionary
+ /// (see 7.8.3, "Resource dictionaries")
+ /// • All resource objects(other than procedure sets) in the resource dictionaries of any
+ /// form XObjects on the page(see 8.10, "Form XObjects")
+ /// • All OPI dictionaries associated with XObjects on the page(see 14.11.7, "Open prepress
+ /// interface (OPI)"). This entry is deprecated in PDF 2.0.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Required)]
+ public const string Version = "/Version";
+
+ ///
+ /// (Required if Version is present; shall be absent if LastModified is present) An array
+ /// of name objects representing the appearance states (value of the AS entry) for annotations
+ /// associated with the page. The appearance states shall be listed in the same order as
+ /// the annotations in the page’s Annots array (see 7.7.3.3, "Page objects"). For an annotation
+ /// with no AS entry, the corresponding array element should be null. No appearance state
+ /// shall be included for the trap network annotation itself.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Required)]
+ public const string AnnotStates = "/AnnotStates";
+
+ ///
+ /// (Optional) An array of font dictionaries representing fonts that were fauxed (replaced
+ /// by substitute fonts) during the generation of trap networks for the page.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string FontFauxing = "/FontFauxing";
+
+ // ReSharper restore InconsistentNaming
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWatermarkAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWatermarkAnnotation.cs
new file mode 100644
index 00000000..f3731884
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWatermarkAnnotation.cs
@@ -0,0 +1,131 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Represents a PDF watermark annotation.
+ ///
+ public sealed class PdfWatermarkAnnotation : PdfAnnotation
+ {
+ // Reference 2.0: 12.5.6.22 Watermark annotations / Page 501
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfWatermarkAnnotation(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfWatermarkAnnotation(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Watermark);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ new class Keys : PdfAnnotation.Keys
+ {
+ // Reference 2.0: Table 193 — Additional entries specific to a watermark annotation / Page 502
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(PdfWatermarkAnnotationFixedPrint))]
+ public const string FixedPrint = "/FixedPrint";
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+
+ ///
+ /// Represents a fixed print dictionary for a PDF watermark annotation.
+ ///
+ public sealed class PdfWatermarkAnnotationFixedPrint : PdfDictionary
+ {
+ // Reference 2.0: 12.5.6.22 Watermark annotations / Page 501
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfWatermarkAnnotationFixedPrint(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfWatermarkAnnotationFixedPrint(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfAnnotation.Keys.Subtype, "/FixedPrint");
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : PdfAnnotation.Keys
+ {
+ // Reference 2.0: Table 194 — Entries in a fixed print dictionary / Page 502
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ ///
+ /// (Optional) The matrix used to transform the annotation’s rectangle before rendering.
+ /// Default value: the identity matrix[1 0 0 1 0 0].
+ /// When positioning content near the edge of the media, this entry should be used to provide
+ /// a reasonable offset to allow for unprintable margins.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string Matrix = "/Matrix";
+
+ ///
+ /// (Optional) The amount to translate the associated content horizontally, as a percentage
+ /// of the width of the target media (or if unknown, the width of the page’s MediaBox).
+ /// 1.0 represents 100% and 0.0 represents 0%. Negative values should not be used, since
+ /// they may cause content to be drawn off the media.
+ /// Default value: 0.
+ ///
+ [KeyInfo(KeyType.Real | KeyType.Optional)]
+ public const string H = "/H";
+
+ ///
+ /// (Optional) The amount to translate the associated content vertically, as a percentage
+ /// of the height of the target media (or if unknown, the height of the page’s MediaBox).
+ /// 1.0 represents 100% and 0.0 represents 0%. Negative values should not be used, since
+ /// they may cause content to be drawn off the media.
+ /// Default value: 0.
+ ///
+ [KeyInfo(KeyType.Real | KeyType.Optional)]
+ public const string V = "/V";
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWidgetAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWidgetAnnotation.cs
index 24d26f45..ad136c83 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWidgetAnnotation.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWidgetAnnotation.cs
@@ -1,59 +1,405 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
+using PdfSharp.Pdf.Forms;
+using PdfSharp.Pdf.Advanced;
+
+// v7.0.0 TODO review
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
namespace PdfSharp.Pdf.Annotations
{
///
- /// Represents a text annotation.
+ /// Represents a PDF widget annotation.
///
- sealed class PdfWidgetAnnotation : PdfAnnotation
+ public sealed class PdfWidgetAnnotation : PdfAnnotation
{
+ // Reference 2.0: 12.5.6.19 Widget annotations / Page 498
+
+ [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")]
public PdfWidgetAnnotation()
+ => throw new InvalidOperationException("PDFsharp 6.4: Use a constructor with a PDF document parameter.");
+
+ public PdfWidgetAnnotation(PdfDocument document)
+ : base(document)
{
Initialize();
}
- public PdfWidgetAnnotation(PdfDocument document)
- : base(document)
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfWidgetAnnotation(PdfDictionary dictionary)
+ : base(dictionary)
{
Initialize();
}
+ internal PdfWidgetAnnotation(PdfFormField acroField)
+ {
+ Document = acroField.Owner;
+
+ Debug.Assert(acroField.IsIndirect);
+
+ var oldElementsFromBaseClasses = Elements;
+ Elements = null!;
+ Elements = acroField.Elements;
+ foreach (var item in oldElementsFromBaseClasses)
+ Elements.Add(item.Key, item.Value);
+
+ // Create new reference. Same ObjectID, but different value.
+ _actualIRef = acroField.Reference;
+ var iref = new PdfReference(acroField, this);
+
+ Initialize();
+ }
+
+ internal static PdfWidgetAnnotation CreateWidgetAnnotationAndField(PdfDocument document)
+ {
+ var field = new PdfFormFieldWidget(document);
+ return new PdfWidgetAnnotation(field);
+ }
+
void Initialize()
{
- Elements.SetName(Keys.Subtype, "/Widget");
+ Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Widget);
+ }
+
+ internal override PdfReference? ActualReference
+ {
+ get => _actualIRef ?? base.ActualReference;
+ }
+ readonly PdfReference? _actualIRef = null;
+
+ internal static bool IsWidgetAnnotation(PdfFormField field)
+ {
+ return field.Elements.GetName(PdfAnnotation.Keys.Subtype) == PdfAnnotationTypeNames.Widget;
+ }
+
+ internal bool IsField()
+ {
+ return _actualIRef?.Value is PdfFormField;
+ }
+
+ internal PdfFormField? GetAsField()
+ {
+ if (_field != null)
+ return _field;
+
+ return _field ??= _actualIRef?.Value as PdfFormField;
+ }
+ PdfFormField? _field;
+
+ ///
+ /// Returns the shared field object or the parent field.
+ /// The returned field may be a field, that shall not be considered a field but simply a widget.
+ /// However, those fields still may have the /V entry set. Therefore, return that field instead of the bottom-most fully qualified field.
+ /// Remember to get the bottom-most fully qualified field to access all the fields widgets, for example.
+ ///
+ public PdfFormField? GetField()
+ {
+ var field = GetAsField();
+ if (field != null)
+ {
+ // This widget is also a field: return the referred field object.
+ return field;
+ }
+
+ field = GetParent();
+
+ if (field == null)
+ PdfSharpLogHost.Logger.LogWarning($"No parent field could be found for the widget annotation with object ID '{Reference?.ObjectID}'.");
+
+ return field;
+ }
+
+
+ PdfFormField? GetParent()
+ {
+ var parentFromKey = Elements.GetDictionary(Keys.Parent);
+
+ var parentFromSearch = FindParent();
+ Debug.Assert(parentFromKey == null || parentFromSearch == parentFromKey, "Check for the correct parent.");
+
+ return parentFromSearch; // By now, we always return the parent from search.
+ }
+
+ PdfFormField? FindParent()
+ {
+ static IEnumerable GetAllFields(IEnumerable fields)
+ {
+ foreach (var field in fields)
+ {
+ yield return field;
+
+ foreach (var kid in GetAllFields(field.GetKids()))
+ yield return kid;
+ }
+ }
+
+ var acroForm = Document.Catalog.GetAcroForm();
+ if (acroForm == null)
+ return null;
+
+ var allFields = GetAllFields(acroForm.Fields);
+
+
+ return allFields.SingleOrDefault(f => f.GetKids().Any(field =>
+ {
+ // Currently, each kid is a field - for a pure widget a PdfFormFieldWidget is added.
+ var widget = field.GetAsWidgetAnnotation();
+ return widget == this;
+ }));
}
///
/// Predefined keys of this dictionary.
///
- internal new class Keys : PdfAnnotation.Keys
+ public new class Keys : PdfAnnotation.Keys
{
+ // Reference 2.0: Table 191 — Additional entries specific to a widget annotation / Page 499
+
+ // ReSharper disable InconsistentNaming
+
///
/// (Optional) The annotation’s highlighting mode, the visual effect to be used when
- /// the mouse button is pressed or held down inside its active area:
- /// N (None) No highlighting.
- /// I (Invert) Invert the contents of the annotation rectangle.
- /// O (Outline) Invert the annotation’s border.
+ /// the mouse button is pressed or held down inside its active area:
+ /// N (None) No highlighting.
+ /// I (Invert) Invert the contents of the annotation rectangle.
+ /// O (Outline) Invert the annotation’s border.
/// P (Push) Display the annotation’s down appearance, if any. If no down appearance is defined,
/// offset the contents of the annotation rectangle to appear as if it were being pushed below
- /// the surface of the page.
- /// T (Toggle) Same as P (which is preferred).
- /// A highlighting mode other than P overrides any down appearance defined for the annotation.
+ /// the surface of the page.
+ /// T (Toggle) Same as P (which is preferred).
+ /// A highlighting mode other than P overrides any down appearance defined for the annotation.
/// Default value: I.
///
[KeyInfo(KeyType.Name | KeyType.Optional)]
public const string H = "/H";
///
- /// (Optional) An appearance characteristics dictionary to be used in constructing a dynamic
- /// appearance stream specifying the annotation’s visual presentation on the page.
+ /// (Optional) An appearance characteristics dictionary that shall be used in constructing a
+ /// dynamic appearance stream specifying the annotation’s visual presentation on the page.
/// The name MK for this entry is of historical significance only and has no direct meaning.
///
[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
public const string MK = "/MK";
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ ///
+ /// (Optional; PDF 1.1) An action that shall be performed when the annotation is activated.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string A = "/A";
+
+ ///
+ /// (Optional; PDF 1.2) An additional-actions dictionary defining the annotation’s behaviour
+ /// in response to various trigger events.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string AA = "/AA";
+
+ ///
+ /// (Optional; PDF 1.2) A border style dictionary specifying the width and dash pattern that
+ /// shall be used in drawing the annotation’s border.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string BS = "/BS";
+
+ ///
+ /// (Required if this widget annotation is one of multiple children in a field; optional otherwise)
+ /// An indirect reference to the widget annotation’s parent field. A widget annotation may have
+ /// at most one parent; that is, it can be included in the Kids array of at most one field.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string Parent = "/Parent";
+
+ // ReSharper restore InconsistentNaming
+
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ internal static string[] WidgetKeys =
+ [
+ // Keys specific to all annotation.
+ // "/Type", comes from AcroFiled
+ "/Subtype", // Can be /Widget
+ "/Rect",
+ "/Contents",
+ "/P",
+ "/NM",
+ "/M",
+ "/F",
+ "/AP",
+ "/AS",
+ "/Border",
+ "/C",
+ "/StructParent",
+ "/OC",
+
+ // Keys specific to a widget annotation.
+ "/H",
+ "/MK",
+ "/A",
+ "/AA",
+ "/BS",
+ "/Parent"
+ ];
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+
+ //readonly PdfFormField? _acroField;
+ }
+}
+
+namespace PdfSharp.Pdf.Annotations // #FILE PdfWidgetAnnotationAppearanceCharacteristics.cs
+{
+ ///
+ /// Represents a text annotation.
+ ///
+ public sealed class PdfWidgetAnnotationAppearanceCharacteristics : PdfDictionary
+ {
+ public PdfWidgetAnnotationAppearanceCharacteristics()
+ { }
+
+ public PdfWidgetAnnotationAppearanceCharacteristics(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfWidgetAnnotationAppearanceCharacteristics(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : KeysBase
+ {
+ // Reference 2.0: 12.5.6.19 Widget annotations / Page 498
+ // Reference 2.0: Table 192 — Entries in an appearance characteristics dictionary / Page 500
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) The number of degrees by which the widget annotation shall be rotated
+ /// counterclockwise relative to the page. The value shall be a multiple of 90.
+ /// Default value: 0.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string R = "/R";
+
+ ///
+ /// (Optional) An array of numbers that shall be in the range 0.0 to 1.0 specifying the colour
+ /// of the widget annotation’s border. The number of array elements determines the colour space
+ /// in which the colour shall be defined:
+ /// 0 No colour; transparent
+ /// 1 DeviceGray
+ /// 3 DeviceRGB
+ /// 4 DeviceCMYK
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string BC = "/BC";
+
+ ///
+ /// (Optional) An array of numbers that shall be in the range 0.0 to 1.0 specifying the colour
+ /// of the widget annotation’s background. The number of array elements shall determine the
+ /// colour space, as described for BC.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string BG = "/BG";
+
+ ///
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string XXX = "/XXX";
+
+ ///
+ /// (Optional; button fields only) The widget annotation’s normal caption, which shall be
+ /// displayed when it is not interacting with the user.
+ /// Unlike the remaining entries listed in this Table, which apply only to widget annotations
+ /// associated with push-button fields, the CA entry may be used with any type of button field,
+ /// including check boxes and radio buttons.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string CA = "/CA";
+
+ ///
+ /// (Optional; push-button fields only) The widget annotation’s rollover caption, which shall
+ /// be displayed when the user rolls the cursor into its active area without pressing the
+ /// mouse button.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string RC = "/RC";
+
+ ///
+ /// (Optional; push-button fields only) The widget annotation’s alternate (down) caption,
+ /// which shall be displayed when the mouse button is pressed within its active area.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string AC = "/AC";
+
+ ///
+ /// (Optional; push-button fields only; shall be an indirect reference) A form XObject
+ /// defining the widget annotation’s normal icon, which shall be displayed when it is not
+ /// interacting with the user.
+ ///
+ [KeyInfo(KeyType.Stream | KeyType.Optional)]
+ public const string I = "/I";
+
+ ///
+ /// (Optional; push-button fields only; shall be an indirect reference) A form XObject defining
+ /// the widget annotation’s rollover icon, which shall be displayed when the user rolls the
+ /// cursor into its active area without pressing the mouse button.
+ ///
+ [KeyInfo(KeyType.Stream | KeyType.Optional)]
+ public const string RI = "/RI";
+
+ ///
+ /// (Optional; push-button fields only; shall be an indirect reference) A form XObject defining
+ /// the widget annotation’s alternate (down) icon, which shall be displayed when the mouse button
+ /// is pressed within its active area.
+ ///
+ [KeyInfo(KeyType.Stream | KeyType.Optional)]
+ public const string IX = "/IX";
+
+ ///
+ /// (Optional; push-button fields only) An icon fit dictionary specifying how the widget
+ /// annotation’s icon shall be displayed within its annotation rectangle. If present, the icon
+ /// fit dictionary shall apply to all of the annotation’s icons (normal, rollover, and alternate).
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string IF = "/IF";
+
+ ///
+ /// (Optional; push-button fields only) A code indicating where to position the text of the widget
+ /// annotation’s caption relative to its icon:
+ /// 0 No icon; caption only
+ /// 1 No caption; icon only
+ /// 2 Caption below the icon
+ /// 3 Caption above the icon
+ /// 4 Caption to the right of the icon
+ /// 5 Caption to the left of the icon
+ /// 6 Caption overlaid directly on the icon
+ /// Default value: 0.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string TP = "/TP";
+
+ // ReSharper restore InconsistentNaming
+
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationFlags.cs
index a5ba1bf0..a1d01b85 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationFlags.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationFlags.cs
@@ -1,6 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+// v7.0.0 Ready
+
namespace PdfSharp.Pdf.Annotations
{
///
@@ -9,78 +11,90 @@ namespace PdfSharp.Pdf.Annotations
[Flags]
public enum PdfAnnotationFlags
{
+ // Reference 2.0: 12.5.3 Annotation flags / Page 470
+
///
- /// If set, do not display the annotation if it does not belong to one of the standard
- /// annotation types and no annotation handler is available. If clear, display such an
- /// unknown annotation using an appearance stream specified by its appearancedictionary,
- /// if any.
+ /// Applies only to annotations which do not belong to one of the standard annotation
+ /// types and for which no annotation handler is available. If set, do not render the
+ /// unknown annotation and do not print it even if the Print flag is set. If clear,
+ /// render such an unknown annotation using an appearance stream specified by its
+ /// appearance dictionary, if any.
///
Invisible = 1 << (1 - 1),
///
- /// (PDF 1.2) If set, do not display or print the annotation or allow it to interact
- /// with the user, regardless of its annotation type or whether an annotation
- /// handler is available. In cases where screen space is limited, the ability to hide
- /// and show annotations selectively can be used in combination with appearance
- /// streams to display auxiliary pop-up information similar in function to online
- /// help systems.
+ /// (PDF 1.2) If set, do not render the annotation or allow it to interact with the user,
+ /// regardless of its annotation type or whether an annotation handler is available.
+ /// NOTE 1
+ /// In cases where screen space is limited, the ability to hide and show annotations
+ /// selectively can be used in combination with appearance streams to render auxiliary
+ /// popup information similar in function to online help systems.
///
Hidden = 1 << (2 - 1),
///
- /// (PDF 1.2) If set, print the annotation when the page is printed. If clear, never
- /// print the annotation, regardless of whether it is displayed on the screen. This
- /// can be useful, for example, for annotations representing interactive pushbuttons,
- /// which would serve no meaningful purpose on the printed page.
+ /// (PDF 1.2) If set, print the annotation when the page is printed unless the Hidden
+ /// flag is also set. If clear, never print the annotation, regardless of whether it
+ /// is rendered on the screen. If the annotation does not contain any appearance streams
+ /// this flag shall be ignored.
+ /// NOTE 2
+ /// This can be useful for annotations representing interactive push-buttons, which
+ /// would serve no meaningful purpose on the printed page.
///
Print = 1 << (3 - 1),
///
/// (PDF 1.3) If set, do not scale the annotation’s appearance to match the magnification
- /// of the page. The location of the annotation on the page (defined by the
- /// upper-left corner of its annotation rectangle) remains fixed, regardless of the
- /// page magnification. See below for further discussion.
+ /// of the page. The location of the annotation on the page (defined by the upper-left
+ /// corner of its annotation rectangle) shall remain fixed, regardless of the page
+ /// magnification. See further discussion following this table.
///
NoZoom = 1 << (4 - 1),
///
- /// (PDF 1.3) If set, do not rotate the annotation’s appearance to match the rotation
- /// of the page. The upper-left corner of the annotation rectangle remains in a fixed
- /// location on the page, regardless of the page rotation. See below for further discussion.
+ /// (PDF 1.3) If set, do not rotate the annotation’s appearance to match the rotation of
+ /// the page. The upper-left corner of the annotation rectangle shall remain in a fixed
+ /// location on the page, regardless of the page rotation. See further discussion
+ /// following this table.
///
NoRotate = 1 << (5 - 1),
///
- /// (PDF 1.3) If set, do not display the annotation on the screen or allow it to
- /// interact with the user. The annotation may be printed (depending on the setting
- /// of the Print flag) but should be considered hidden for purposes of on-screen
- /// display and user interaction.
+ /// (PDF 1.3) If set, do not render the annotation on the screen or allow it to interact
+ /// with the user. The annotation may be printed (depending on the setting of the Print flag)
+ /// but should be considered hidden for purposes of on-screen display and user interaction.
///
NoView = 1 << (6 - 1),
///
- /// (PDF 1.3) If set, do not allow the annotation to interact with the user. The
- /// annotation may be displayed or printed (depending on the settings of the
- /// NoView and Print flags) but should not respond to mouse clicks or change its
- /// appearance in response to mouse motions.
- /// Note: This flag is ignored for widget annotations; its function is subsumed by
- /// the ReadOnly flag of the associated form field.
+ /// (PDF 1.3) If set, do not allow the annotation to interact with the user. The annotation
+ /// may be rendered or printed (depending on the settings of the NoView and Print flags)
+ /// but should not respond to mouse clicks or change its appearance in response to mouse
+ /// motions.
+ /// This flag shall be ignored for widget annotations; its function is subsumed by the
+ /// ReadOnly flag of the associated form field.
///
ReadOnly = 1 << (7 - 1),
///
/// (PDF 1.4) If set, do not allow the annotation to be deleted or its properties
- /// (including position and size) to be modified by the user. However, this flag does
- /// not restrict changes to the annotation’s contents, such as the value of a form
- /// field.
+ /// (including position and size) to be modified by the user. However, this flag does not
+ /// restrict changes to the annotation’s contents, such as the value of a form field.
///
Locked = 1 << (8 - 1),
///
- /// (PDF 1.5) If set, invert the interpretation of the NoView flag for certain events.
- /// A typical use is to have an annotation that appears only when a mouse cursor is
- /// held over it.
+ /// (PDF 1.5) If set, invert the interpretation of the NoView flag for annotation selection
+ /// and mouse hovering, causing the annotation to be visible when the mouse pointer hovers
+ /// over the annotation or when the annotation is selected.
///
ToggleNoView = 1 << (9 - 1),
+
+ ///
+ /// (PDF 1.7) If set, do not allow the contents of the annotation to be modified by the user. This
+ /// flag does not restrict deletion of the annotation or changes to other annotation properties,
+ /// such as position and size.
+ ///
+ LockedContents = 1 << (10 - 1),
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationStates.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationStates.cs
new file mode 100644
index 00000000..284db5b4
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationStates.cs
@@ -0,0 +1,56 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// v7.0.0 TODO
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Specifies the annotation types.
+ ///
+ [Flags]
+ public enum PdfAnnotationStates
+ {
+ // Reference 2.0: Table 174 — Annotation states / Page 481
+
+ ///
+ /// The user has indicated nothing about the change (the default).
+ ///
+ None,
+
+ // ----- States if the state model is Marked --------------------------------------------------------
+
+ ///
+ /// The annotation has been marked by the user.
+ /// Markup: Yes
+ ///
+ Marked,
+
+ ///
+ /// The annotation has not been marked by the user (the default).
+ ///
+ Unmarked,
+
+ // ----- States if the state model is Review --------------------------------------------------------
+
+ ///
+ /// The user agrees with the change.
+ ///
+ Accepted,
+
+ ///
+ /// The user disagrees with the change.
+ ///
+ Rejected,
+
+ ///
+ /// The change has been cancelled.
+ ///
+ Cancelled,
+
+ ///
+ /// The change has been completed.
+ ///
+ Completed
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationTypes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationTypes.cs
new file mode 100644
index 00000000..423b88ac
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationTypes.cs
@@ -0,0 +1,373 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// v7.0.0 Ready
+
+namespace PdfSharp.Pdf.Annotations
+{
+ ///
+ /// Specifies the annotation types.
+ ///
+ public enum PdfAnnotationTypes
+ {
+ // Reference 2.0: 12.5.6 Annotation types / Page 476
+ // Reference 2.0: Table 171 — Annotation types / Page 476
+
+ ///
+ /// Text annotation.
+ /// Markup: Yes
+ ///
+ Text = 1,
+
+ ///
+ /// Link annotation.
+ /// Markup: No
+ ///
+ Link,
+
+ ///
+ /// (PDF 1.3) Free text annotation.
+ /// Markup: Yes
+ ///
+ FreeText,
+
+ ///
+ /// (PDF 1.3) Line annotation.
+ /// Markup: Yes
+ ///
+ Line,
+
+ ///
+ /// (PDF 1.3) Square annotation.
+ /// Markup: Yes
+ ///
+ Square,
+
+ ///
+ /// (PDF 1.3) Circle annotation.
+ /// Markup: Yes
+ ///
+ Circle,
+
+ ///
+ /// (PDF 1.5) Polygon annotation.
+ /// Markup: Yes
+ ///
+ Polygon,
+
+ ///
+ /// (PDF 1.5) Polyline annotation.
+ /// Markup: Yes
+ ///
+ PolyLine,
+
+ ///
+ /// (PDF 1.3) Highlight annotation.
+ /// Markup: Yes
+ ///
+ Highlight,
+
+ ///
+ /// (PDF 1.3) Underline annotation.
+ /// Markup: Yes
+ ///
+ Underline,
+
+ ///
+ /// (PDF 1.4) Squiggly-underline annotation.
+ /// Markup: Yes
+ ///
+ Squiggly,
+
+ ///
+ /// (PDF 1.3) Strikeout annotation..
+ /// Markup: Yes
+ ///
+ StrikeOut,
+
+ ///
+ /// (PDF 1.5) Caret annotation.
+ /// Markup: Yes
+ ///
+ Caret,
+
+ ///
+ /// (PDF 1.3) Rubber stamp annotation.
+ /// Markup: Yes
+ ///
+ [Obsolete("Use Stamp instead.")]
+ RubberStamp,
+
+ ///
+ /// (PDF 1.3) Rubber stamp annotation.
+ /// Markup: Yes
+ ///
+ Stamp,
+
+ ///
+ /// (PDF 1.3) Ink annotation.
+ /// Markup: Yes
+ ///
+ Ink,
+
+ ///
+ /// (PDF 1.3) Popup annotation.
+ /// Markup: No
+ ///
+ Popup,
+
+ ///
+ /// (PDF 1.3) File attachment annotation.
+ /// Markup: Yes
+ ///
+ FileAttachment,
+
+ ///
+ /// (PDF 1.2; deprecated in PDF 2.0) Sound annotation.
+ /// Markup: Yes
+ ///
+ Sound,
+
+ ///
+ /// (PDF 1.2; deprecated in PDF 2.0) Movie annotation.
+ /// Markup: No
+ ///
+ Movie,
+
+ ///
+ /// (PDF 1.5) Screen annotation.
+ /// Markup: No
+ ///
+ Screen,
+
+ ///
+ /// (PDF 1.2) Widget annotation.
+ /// Markup: No
+ ///
+ Widget,
+
+ ///
+ /// (PDF 1.4) Printer’s mark annotation.
+ /// Markup: No
+ ///
+ PrinterMark,
+
+ ///
+ /// (PDF 1.3; deprecated in PDF 2.0) Trap network annotation.
+ /// Markup: No
+ ///
+ TrapNet,
+
+ ///
+ /// (PDF 1.6) Watermark annotation.
+ /// Markup: No
+ ///
+ Watermark,
+
+ ///
+ /// (PDF 1.6) 3D annotation.
+ /// Markup: No
+ ///
+ ThreeD, // Use '3D', not 'nameof(ThreeD)'.
+
+ ///
+ /// (PDF 1.7) Redact annotation.
+ /// Markup: Yes
+ ///
+ Redact,
+
+ ///
+ /// (PDF 2.0) Projection annotation.
+ /// Markup: Yes
+ ///
+ Projection,
+
+ ///
+ /// (PDF 2.0) RichMedia annotation.
+ /// Markup: No
+ ///
+ RichMedia
+ }
+}
+
+namespace PdfSharp.Pdf.Annotations
+{
+#pragma warning disable CS0414 // Field is assigned but its value is never used // DELETE
+
+ ///
+ /// Specifies the annotation types.
+ ///
+ public static class PdfAnnotationTypeNames
+ {
+ // Reference 2.0: 12.5.6 Annotation types / Page 476
+ // Reference 2.0: Table 171 — Annotation types / Page 476
+
+ ///
+ /// Text annotation.
+ /// Markup: Yes
+ ///
+ public const string Text = "/" + nameof(PdfAnnotationTypes.Text);
+
+ ///
+ /// Link annotation.
+ /// Markup: No
+ ///
+ public const string Link = "/" + nameof(PdfAnnotationTypes.Link);
+
+ ///
+ /// (PDF 1.3) Free text annotation.
+ /// Markup: Yes
+ ///
+ public const string FreeText = "/" + nameof(PdfAnnotationTypes.FreeText);
+
+ ///
+ /// (PDF 1.3) Line annotation.
+ /// Markup: Yes
+ ///
+ public const string Line = "/" + nameof(PdfAnnotationTypes.Line);
+
+ ///
+ /// (PDF 1.3) Square annotation.
+ /// Markup: Yes
+ ///
+ public const string Square = "/" + nameof(PdfAnnotationTypes.Square);
+
+ ///
+ /// (PDF 1.3) Circle annotation.
+ /// Markup: Yes
+ ///
+ public const string Circle = "/" + nameof(PdfAnnotationTypes.Circle);
+
+ ///
+ /// (PDF 1.5) Polygon annotation.
+ /// Markup: Yes
+ ///
+ public const string Polygon = "/" + nameof(PdfAnnotationTypes.Polygon);
+
+ ///
+ /// (PDF 1.5) Polyline annotation.
+ /// Markup: Yes
+ ///
+ public const string PolyLine = "/" + nameof(PdfAnnotationTypes.PolyLine);
+
+ ///
+ /// (PDF 1.3) Highlight annotation.
+ /// Markup: Yes
+ ///
+ public const string Highlight = "/" + nameof(PdfAnnotationTypes.Highlight);
+
+ ///
+ /// (PDF 1.3) Underline annotation.
+ /// Markup: Yes
+ ///
+ public const string Underline = "/" + nameof(PdfAnnotationTypes.Underline);
+
+ ///
+ /// (PDF 1.4) Squiggly-underline annotation.
+ /// Markup: Yes
+ ///
+ public const string Squiggly = "/" + nameof(PdfAnnotationTypes.Squiggly);
+
+ ///
+ /// (PDF 1.3) Strikeout annotation..
+ /// Markup: Yes
+ ///
+ public const string StrikeOut = "/" + nameof(PdfAnnotationTypes.StrikeOut);
+
+ ///
+ /// (PDF 1.5) Caret annotation.
+ /// Markup: Yes
+ ///
+ public const string Caret = "/" + nameof(PdfAnnotationTypes.Caret);
+
+ ///
+ /// (PDF 1.3) Rubber stamp annotation.
+ /// Markup: Yes
+ ///
+ public const string Stamp = "/" + nameof(PdfAnnotationTypes.Stamp);
+
+ ///
+ /// (PDF 1.3) Ink annotation.
+ /// Markup: Yes
+ ///
+ public const string Ink = "/" + nameof(PdfAnnotationTypes.Ink);
+
+ ///
+ /// (PDF 1.3) Popup annotation.
+ /// Markup: No
+ ///
+ public const string Popup = "/" + nameof(PdfAnnotationTypes.Popup);
+
+ ///
+ /// (PDF 1.3) File attachment annotation.
+ /// Markup: Yes
+ ///
+ public const string FileAttachment = "/" + nameof(PdfAnnotationTypes.FileAttachment);
+
+ ///
+ /// (PDF 1.2; deprecated in PDF 2.0) Sound annotation.
+ /// Markup: Yes
+ ///
+ public const string Sound = "/" + nameof(PdfAnnotationTypes.Sound);
+
+ ///
+ /// (PDF 1.2; deprecated in PDF 2.0) Movie annotation.
+ /// Markup: No
+ ///
+ public const string Movie = "/" + nameof(PdfAnnotationTypes.Movie);
+
+ ///
+ /// (PDF 1.5) Screen annotation.
+ /// Markup: No
+ ///
+ public const string Screen = "/" + nameof(PdfAnnotationTypes.Screen);
+
+ ///
+ /// (PDF 1.2) Widget annotation.
+ /// Markup: No
+ ///
+ public const string Widget = "/" + nameof(PdfAnnotationTypes.Widget);
+
+ ///
+ /// (PDF 1.4) Printer’s mark annotation.
+ /// Markup: No
+ ///
+ public const string PrinterMark = "/" + nameof(PdfAnnotationTypes.PrinterMark);
+
+ ///
+ /// (PDF 1.3; deprecated in PDF 2.0) Trap network annotation.
+ /// Markup: No
+ ///
+ public const string TrapNet = "/" + nameof(PdfAnnotationTypes.TrapNet);
+
+ ///
+ /// (PDF 1.6) Watermark annotation.
+ /// Markup: No
+ ///
+ public const string Watermark = "/" + nameof(PdfAnnotationTypes.Watermark);
+
+ ///
+ /// (PDF 1.6) 3D annotation.
+ /// Markup: No
+ ///
+ public const string ThreeD = "/3D";
+
+ ///
+ /// (PDF 1.7) Redact annotation.
+ /// Markup: Yes
+ ///
+ public const string Redact = "/" + nameof(PdfAnnotationTypes.Redact);
+
+ ///
+ /// (PDF 2.0) Projection annotation.
+ /// Markup: Yes
+ ///
+ public const string Projection = "/" + nameof(PdfAnnotationTypes.Projection);
+
+ ///
+ /// (PDF 2.0) RichMedia annotation.
+ /// Markup: No
+ ///
+ public const string RichMedia = "/" + nameof(PdfAnnotationTypes.RichMedia);
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfRubberStampAnnotationIcon.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfStampAnnotationIcons.cs
similarity index 89%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfRubberStampAnnotationIcon.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfStampAnnotationIcons.cs
index fae16dd9..0855aef4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfRubberStampAnnotationIcon.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfStampAnnotationIcons.cs
@@ -1,13 +1,17 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
namespace PdfSharp.Pdf.Annotations
{
+ // v7.0.0 Ready
+
///
/// Specifies the predefined icon names of rubber stamp annotations.
///
- public enum PdfRubberStampAnnotationIcon
+ public enum PdfStampAnnotationIcons
{
+ // Reference 2.0: Table 184 — Additional entries specific to a rubber stamp annotation / Page 494
+
///
/// A pre-defined rubber stamp annotation icon.
///
@@ -21,32 +25,32 @@ public enum PdfRubberStampAnnotationIcon
///
/// A pre-defined rubber stamp annotation icon.
///
- AsIs,
+ Experimental,
///
/// A pre-defined rubber stamp annotation icon.
///
- Confidential,
+ NotApproved,
///
/// A pre-defined rubber stamp annotation icon.
///
- Departmental,
+ AsIs,
///
/// A pre-defined rubber stamp annotation icon.
///
- Draft,
+ Expired,
///
/// A pre-defined rubber stamp annotation icon.
///
- Experimental,
+ NotForPublicRelease,
///
/// A pre-defined rubber stamp annotation icon.
///
- Expired,
+ Confidential,
///
/// A pre-defined rubber stamp annotation icon.
@@ -56,31 +60,31 @@ public enum PdfRubberStampAnnotationIcon
///
/// A pre-defined rubber stamp annotation icon.
///
- ForComment,
+ Sold,
///
/// A pre-defined rubber stamp annotation icon.
///
- ForPublicRelease,
+ Departmental,
///
/// A pre-defined rubber stamp annotation icon.
///
- NotApproved,
+ ForComment,
///
/// A pre-defined rubber stamp annotation icon.
///
- NotForPublicRelease,
+ TopSecret,
///
/// A pre-defined rubber stamp annotation icon.
///
- Sold,
+ Draft,
///
/// A pre-defined rubber stamp annotation icon.
///
- TopSecret,
+ ForPublicRelease
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcon.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcons.cs
similarity index 54%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcon.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcons.cs
index 715fa844..07db8acb 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcon.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcons.cs
@@ -1,51 +1,55 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+// v7.0.0 Ready
+
namespace PdfSharp.Pdf.Annotations
{
///
/// Specifies the pre-defined icon names of text annotations.
///
- public enum PdfTextAnnotationIcon
+ public enum PdfTextAnnotationIcons
{
+ // Reference 2.0: Table 175 — Values for Name key / Page 482
+
///
- /// A pre-defined annotation icon.
+ /// A pre-defined text annotation icon.
///
NoIcon,
///
- /// A pre-defined annotation icon.
+ /// A pre-defined text annotation icon.
///
Comment,
///
- /// A pre-defined annotation icon.
+ /// A pre-defined text annotation icon.
///
- Help,
+ Key,
///
- /// A pre-defined annotation icon.
+ /// A pre-defined text annotation icon.
///
- Insert,
+ Note,
///
- /// A pre-defined annotation icon.
+ /// A pre-defined text annotation icon.
///
- Key,
+ Help,
///
- /// A pre-defined annotation icon.
+ /// A pre-defined text annotation icon.
///
NewParagraph,
///
- /// A pre-defined annotation icon.
+ /// A pre-defined text annotation icon.
///
- Note,
+ Paragraph,
///
- /// A pre-defined annotation icon.
+ /// A pre-defined text annotation icon.
///
- Paragraph,
+ Insert
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFileInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFileInfo.cs
new file mode 100644
index 00000000..19abe3cb
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFileInfo.cs
@@ -0,0 +1,53 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Attachments
+{
+ ///
+ /// Sums up all relevant information of an embedded file in a PDF file.
+ ///
+ public class EmbeddedFileInfo
+ {
+ ///
+ /// The key of the PdfFileSpecification in the /Names array of the /EmbeddedFiles name tree.
+ ///
+ public string NamesKey { get; set; } = "";
+
+ ///
+ /// Gets or sets the name of the file.
+ ///
+ public string FileName { get; set; } = "";
+
+ ///
+ /// Gets or sets the file mime type.
+ ///
+ public string FileType { get; set; } = "";
+
+ ///
+ /// Gets or sets an optional description of the file.
+ ///
+ public string Description { get; set; } = "";
+
+ ///
+ /// Gets or sets the optional creation time of the file.
+ ///
+ public DateTimeOffset? CreationTime { get; set; }
+
+ ///
+ /// Gets or sets the optional modification time of the file.
+ ///
+ public DateTimeOffset? ModificationTime { get; set; }
+
+ ///
+ /// Gets or sets the bytes of the file.
+ ///
+ public byte[] Data { get; set; } = [];
+
+ ///
+ /// Gets or sets the PdfAFRelationship of the file.
+ /// See PDF specification for further details.
+ ///
+ // ReSharper disable once InconsistentNaming
+ public string AFRelationship { get; set; } = PdfAFRelationship.Unspecified;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFilesManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFilesManager.cs
new file mode 100644
index 00000000..76914bec
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFilesManager.cs
@@ -0,0 +1,140 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Internal;
+
+namespace PdfSharp.Pdf.Attachments
+{
+ ///
+ /// Provides functionality to add and retrieve embedded files from a PDF document.
+ ///
+ public class EmbeddedFilesManager : ManagerBase
+ {
+ EmbeddedFilesManager(PdfDocument document) : base(document)
+ {
+ _document = document;
+
+ switch (_document.State)
+ {
+ case DocumentState.Created:
+ InitializeNewDocument();
+ break;
+
+ case DocumentState.Imported:
+ InitializeImportedDocument();
+ break;
+
+ case DocumentState.Disposed:
+ case DocumentState.Saved:
+ default:
+ throw new InvalidOperationException($"Document is in state '{document.State}' and cannot be modified anymore.");
+ }
+ }
+
+ void InitializeNewDocument()
+ { }
+
+ void InitializeImportedDocument()
+ {
+ var catalog = _document.Catalog;
+ var x = catalog.Names;
+ }
+
+ ///
+ /// Gets the number of embedded files of this document.
+ ///
+ public int FileCount => EmbeddedFiles?.FileCount ?? 0; // ChatGPT suggests FileCount over FilesCount.
+
+ ///
+ /// Gets the keys of all embedded files from the /Names array.
+ ///
+ public string[] NamesKeys // Here we use NamesKey because of the /Names array.
+ {
+ get
+ {
+ var embeddedFiles = _document.Catalog.Names.GetEmbeddedFiles(true);
+ return embeddedFiles.Names?.NamesKeys ?? [];
+ }
+ }
+
+ ///
+ /// Embeds the specified file in the PDF document.
+ ///
+ ///
+ ///
+ public void AddFile(EmbeddedFileInfo fileInfo, bool compressStream = true)
+ {
+ //// TODO: Should return non-nullable object.
+ //var embeddedFiles = _document.Catalog.Names.GetEmbeddedFiles(true)
+ // ?? throw new InvalidOperationException(SyMsgs.UnexpectedNullValueRetrieved(
+ // $"Function: {nameof(EmbeddedFilesManager)}.{nameof(AddFile)}.").Message);
+
+ var embeddedFiles = _document.Catalog.Names.GetEmbeddedFiles(true);
+
+ //var embeddedFileStream = new PdfEmbeddedFileStream(_document, fileInfo.Data, fileInfo.FileType,
+ // fileInfo.ModificationTime); // TODO compress
+ //var fileSpecification = new PdfFileSpecification(_document, embeddedFileStream, fileInfo.FileName);
+
+ var fileSpecification = new PdfFileSpecification(_document, fileInfo);
+ embeddedFiles.AddFileSpecification(fileInfo.NamesKey, fileSpecification);
+ }
+
+ ///
+ /// Gets an EmbeddedFileInfo for the embedded file with the specified index.
+ ///
+ /// Index of the embedded file in range [0..FileCount].
+ public EmbeddedFileInfo GetEmbeddedFileInfo(int index)
+ {
+ var ef = EmbeddedFiles;
+ if (ef == null)
+ throw new InvalidOperationException("Document has no embedded files.");
+
+ return ef.GetFileSpecification(index).GetFileInfo();
+ }
+
+ ///
+ /// Gets an EmbeddedFileInfo for the embedded file with the specified /Names key.
+ ///
+ /// The key of the embedded file in the /Names array.
+ ///
+ public EmbeddedFileInfo? GetEmbeddedFileInfo(string namesKey)
+ {
+ var ef = EmbeddedFiles;
+ if (ef == null)
+ return null;
+
+ return ef.GetFileSpecification(namesKey)?.GetFileInfo() ?? null;
+ }
+
+ ///
+ /// Get the /EmbeddedFiles name tree dictionary from the catalogs /Names entry, or null,
+ /// if no such entry exists.
+ /// Use this property to get direct access to the PDF objects that describes the embedded files.
+ ///
+ PdfEmbeddedFiles? EmbeddedFiles
+ {
+ get
+ {
+ var catalog = _document.Catalog;
+ if (catalog.HasNames)
+ {
+ var names = catalog.Names;
+ if (names.HasEmbeddedFiles)
+ {
+ var embeddedFiles = names.GetEmbeddedFiles();
+ return embeddedFiles;
+ }
+ }
+ return null;
+ }
+ }
+
+ ///
+ /// Gets or creates the EmbeddedFilesManager for the specified document.
+ ///
+ public static EmbeddedFilesManager ForDocument(PdfDocument document)
+ => document.EmbeddedFilesManager ??= new(document);
+
+ readonly PdfDocument _document;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfAFRelationship.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfAFRelationship.cs
new file mode 100644
index 00000000..9bf17473
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfAFRelationship.cs
@@ -0,0 +1,58 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Attachments
+{
+ ///
+ /// Specifies the associated files relationship.
+ /// (Optional; PDF 2.0) A name value that represents the relationship between the component of this PDF document
+ /// that refers to this file specification and the associated file denoted by this file specification dictionary.
+ /// These values represent the following relationships:
+ ///
+ // ReSharper disable once InconsistentNaming
+ public static class PdfAFRelationship // TODO: clean up #US322
+ {
+ ///
+ /// shall be used when the relationship is not known or cannot be described using one of the other values.
+ /// Unspecified is to be used only when no other value correctly reflects the relationship.
+ /// NOTE 3 The value of AFRelationship does not explicitly provide any processing instructions for a PDF processor.It is provided for information and semantic purposes for those processors that are able to use such additional information.
+ ///
+ public const string Unspecified = nameof(Unspecified);
+
+ ///
+ /// shall be used if this file specification is the original source material for the associated content.
+ ///
+ public const string Source = nameof(Source);
+
+ ///
+ /// shall be used if this file specification represents information used to derive a visual presentation – such as for a table or a graph.
+ ///
+ public const string Data = nameof(Data);
+
+ ///
+ /// shall be used if this file specification is an alternative representation of content, for example audio.
+ ///
+ public const string Alternative = nameof(Alternative);
+
+ ///
+ /// shall be used if this file specification represents a supplemental representation of the original source or data that may be more easily consumable (e.g., A MathML version of an equation).
+ ///
+ public const string Supplement = nameof(Supplement);
+
+ ///
+ /// shall be used if this file specification is an encrypted payload document that should be displayed to the user if the PDF processor has the cryptographic filter needed to decrypt the document.
+ ///
+ public const string EncryptedPayload = nameof(EncryptedPayload);
+
+ ///
+ /// shall be used if this file specification is the data associated with the AcroForm (see 12.7.3, "Interactive form dictionary") of this PDF.
+ ///
+ public const string FormData = nameof(FormData);
+
+ ///
+ /// shall be used if this file specification is a schema definition for the associated object (e.g. an XML schema associated with a metadata stream).
+ ///
+ public const string Schema = nameof(Schema);
+ }
+}
+
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfEmbeddedFiles.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfEmbeddedFiles.cs
new file mode 100644
index 00000000..663f0d6c
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfEmbeddedFiles.cs
@@ -0,0 +1,132 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+
+namespace PdfSharp.Pdf.Attachments
+{
+ ///
+ /// Represents the name tree /EmbeddedFiles of the document’s catalog /Names dictionary.
+ ///
+ public class PdfEmbeddedFiles : PdfNameTreeNode
+ {
+ // Reference 2.0: 7.7.4 Name dictionary: Table 32 — Entries in the name dictionary / Page 110
+
+ ///
+ /// Initialize a new instance of this class.
+ ///
+ public PdfEmbeddedFiles()
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfEmbeddedFiles(PdfDictionary dict)
+ : base(dict)
+ {
+ InitializeExisting();
+ }
+
+ void InitializeExisting()
+ {
+ // Transform /Names values to PdfFileSpecification.
+ var names = Names;
+ if (names != null)
+ {
+ for (int idx = 0; idx < names.Count; idx++)
+ {
+ var abc = names[0];
+ var item = abc.Value;
+ if (abc.Value is PdfFileSpecification)
+ continue;
+
+ //var fc = PdfObjectsHelper.TransformItem(item);
+ //var fc = PdfObjectsHelper.TransformArrayItem(names, idx * 2 + 1);
+#if true
+ // #warning TODO Check transformation in ArrayElements.GetItemInternal(...)
+ var fc = names.Elements.GetValue(idx * 2 + 1);
+#else
+ var fc = PdfObjectsHelper.TransformArrayItem(names, idx * 2 + 1);
+#endif
+ // /*v*/ar fc
+
+
+ // HACK:
+ var test = names.Elements[1];
+ }
+ }
+ }
+
+ ///
+ /// Gets the number of embedded files.
+ ///
+ public int FileCount => Names?.Count ?? 0;
+
+ ///
+ /// Gets the PDF file specification of the embedded file with the specified index.
+ ///
+ /// The 0-based index.
+ public PdfFileSpecification GetFileSpecification(int index)
+ {
+ var entry = Names?[index];
+ if (entry == null)
+ throw new InvalidOperationException("Name tree has no /Names entry."); // #MSG
+
+ var item = entry.Value;
+ PdfReference.Dereference(ref item);
+ if (item is PdfFileSpecification fs)
+ {
+ fs.NamesKey = entry.Key.Value;
+ return fs;
+ }
+
+ // TODO Should not come here.
+ return (PdfFileSpecification)item;
+ }
+
+ ///
+ /// Gets the PDF file specification of the embedded file with the specified key,
+ /// or null, if no file with the key exists..
+ ///
+ /// The name of the file.
+ public PdfFileSpecification? GetFileSpecification(string key)
+ {
+ var item = Names?[key];
+ if (item == null)
+ return null;
+ PdfReference.Dereference(ref item);
+ if (item is PdfFileSpecification fs)
+ return fs;
+
+ Debugger.Break(); // TODO
+ return null;
+ }
+
+ ///
+ /// Adds a new embedded file defined by a PDF file specification to the document
+ ///
+ /// The name of the file.
+ /// A PDF file specification.
+ public void AddFileSpecification(string key, PdfFileSpecification fileSpec)
+ {
+ PdfDocument owner = OwningDocument;
+
+ if (fileSpec.Reference is null)
+ {
+ // Make object indirect.
+ owner.Internals.AddObject(fileSpec);
+ }
+
+ // Add it to the name tree.
+ if (String.IsNullOrEmpty(key))
+ key = fileSpec.Name;
+ AddName(key, fileSpec);
+
+ // Add it to the /AF catalog entry.
+ // It’s a PDF 2.0 feature, but some producer apps use it in PDF 1.7 documents.
+ var af = owner.Catalog.Elements.GetRequiredArray(PdfCatalog.Keys.AF, VCF.Create);
+ af.AddDictionary(fileSpec);
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFileSpecification.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfFileSpecification.cs
similarity index 55%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFileSpecification.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfFileSpecification.cs
index 09da5b41..d724f2fe 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFileSpecification.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfFileSpecification.cs
@@ -1,42 +1,172 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.Advanced
+using PdfSharp.Internal;
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.PdfItemExtensions;
+
+namespace PdfSharp.Pdf.Attachments
{
///
- /// Represents a file specification dictionary.
+ /// Represents a PDF file specification dictionary.
///
public class PdfFileSpecification : PdfDictionary
{
+ // TODO: move file from PdfSharp.Pdf.Advanced to PdfSharp.Pdf.Attachments.
+
+ // Reference 2.0: 7.11.3 File specification dictionaries / Page 134
+
///
- /// Initializes a new instance of PdfFileSpecification referring an embedded file stream.
+ /// Initializes a new instance of PdfFileSpecification referring an embedded file info.
///
- public PdfFileSpecification(PdfDocument document, PdfEmbeddedFileStream embeddedFileStream, string name) : base(document)
+ public PdfFileSpecification(PdfDocument document, EmbeddedFileInfo embeddedFileInfo)
+ : base(document)
{
- _embeddedFileStream = embeddedFileStream;
- _name = name;
+ // ReSharper disable once StringLiteralTypo
+ Elements.SetName(Keys.Type, "/Filespec");
+
+ Elements.SetString(Keys.F, embeddedFileInfo.FileName);
+ Elements.SetString(Keys.UF, embeddedFileInfo.FileName);
+
+ Elements.SetName(Keys.AFRelationship, embeddedFileInfo.AFRelationship);
- Initialize();
+ if (!String.IsNullOrEmpty(embeddedFileInfo.Description))
+ Elements.SetString(Keys.Desc, embeddedFileInfo.Description);
+
+ var embeddedFileStream = new PdfEmbeddedFileStream(document, embeddedFileInfo.Data,
+ embeddedFileInfo.FileType,
+ embeddedFileInfo.ModificationTime); // TODO compress
+ document.Internals.AddObject(embeddedFileStream);
+
+ var embeddedFileDictionary = new PdfDictionary(document);
+ embeddedFileDictionary.Elements.SetReference(Keys.F,
+ embeddedFileStream.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull());
+ embeddedFileDictionary.Elements.SetReference(Keys.UF, embeddedFileStream.Reference);
+
+ Elements.SetObject(Keys.EF, embeddedFileDictionary);
}
- void Initialize()
+ ///
+ /// Initializes a new instance of PdfFileSpecification referring an embedded file stream.
+ ///
+ [Obsolete("Use PdfFileSpecification constructor with EmbeddedFileInfo parameter.")]
+ public PdfFileSpecification(PdfDocument document, PdfEmbeddedFileStream embeddedFileStream, string name)
+ : base(document)
{
+ // ReSharper disable once StringLiteralTypo
Elements.SetName(Keys.Type, "/Filespec");
- Elements.SetString(Keys.F, _name);
- Elements.SetString(Keys.UF, _name);
+ Elements.SetString(Keys.F, name);
+ Elements.SetString(Keys.UF, name);
var embeddedFileDictionary = new PdfDictionary(Owner);
- Owner.Internals.AddObject(_embeddedFileStream);
- embeddedFileDictionary.Elements.SetReference(Keys.F, _embeddedFileStream.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull());
- embeddedFileDictionary.Elements.SetReference(Keys.UF, _embeddedFileStream.Reference);
+ Owner.Internals.AddObject(embeddedFileStream);
+ embeddedFileDictionary.Elements.SetReference(Keys.F,
+ embeddedFileStream.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull());
+ embeddedFileDictionary.Elements.SetReference(Keys.UF, embeddedFileStream.Reference);
Elements.SetObject(Keys.EF, embeddedFileDictionary);
}
- readonly PdfEmbeddedFileStream _embeddedFileStream;
- readonly string _name;
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFileSpecification(PdfDictionary dict)
+ : base(dict)
+ {
+ // TODO: Nothing???
+ }
+
+ ///
+ /// Gets the name of the file, or the empty string,
+ /// if no name exists.
+ ///
+ public string Name
+ {
+ get
+ {
+#if true
+ if (Elements.TryGetString(Keys.UF, out var name))
+ return name;
+ if (Elements.TryGetString(Keys.F, out name))
+ return name;
+ return "";
+#else
+ var name = Elements.GetString(Keys.UF);
+ if (String.IsNullOrEmpty(name))
+ name = Elements.GetString(Keys.F);
+ return name;
+#endif
+ }
+ }
+
+ internal string NamesKey { get; set; } = "";
+
+ ///
+ /// Gets a summary of all relevant information of this file specification and
+ /// the associated embedded file stream.
+ ///
+ public EmbeddedFileInfo GetFileInfo()
+ {
+ var ef = Elements.GetDictionary(Keys.EF);
+ if (ef == null)
+ throw new InvalidOperationException("Key not found: /EF"); // #MSG
+
+ var f = ef.Elements.GetDictionary("/F") // This is not Keys.F.
+ ?? ef.Elements.GetDictionary("/UF"); // This is not Keys.UF.
+ if (f == null)
+ throw new InvalidOperationException(
+ "The PdfFileSpecification’s /EF dictionary does neither contain an /F nor an /AF key. " +
+ "No PdfEmbeddedFileStream found."); // #msg
+
+ var efs = f;
+
+ // HACK
+ if (efs.GetType() != typeof(PdfEmbeddedFileStream))
+ {
+ // Should not come here.
+ Debugger.Break();
+ if (!efs.IsIndirect)
+ throw new InvalidOperationException("File stream must be indirect object."); // #MSG
+
+ efs = new PdfEmbeddedFileStream(efs);
+ }
+
+ var info = new EmbeddedFileInfo
+ {
+ NamesKey = NamesKey
+ };
+
+ var name = Elements.GetString(Keys.F);
+ if (!String.IsNullOrEmpty(name))
+ info.FileName = name;
+
+ var type = efs.Elements.GetString(PdfEmbeddedFileStream.Keys.Subtype);
+ if (!String.IsNullOrEmpty(type))
+ info.FileType = type;
+
+ var desc = Elements.GetString(Keys.Desc);
+ if (!String.IsNullOrEmpty(desc))
+ info.Description = desc;
+
+ //var @params = efs.Elements[PdfEmbeddedFileStream.Keys.Params].AsDictionary();
+ var @params = efs.Elements.GetDictionary(PdfEmbeddedFileStream.Keys.Params); // TODO #US373
+ if (@params != null) // TODO: StL: params is never null.
+ {
+ if (@params.Elements.TryGetDateTime(PdfEmbeddedFileParameters.Keys.CreationDate, out var creationDate))
+ info.CreationTime = creationDate;
+
+ if (@params.Elements.TryGetDateTime(PdfEmbeddedFileParameters.Keys.ModDate, out var modificationTime))
+ info.ModificationTime = modificationTime;
+ }
+
+ if (efs.Stream != null)
+ info.Data = efs.Stream.UnfilteredValue;
+
+ return info;
+ }
///
/// Predefined keys of this dictionary.
@@ -156,12 +286,12 @@ public class Keys : KeysBase
//[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
//public const string RF = "/RF";
- /////
- ///// (Optional; PDF 1.6) Descriptive text associated with the file specification. It is used for
- ///// files in the EmbeddedFiles name tree (see Section 3.6.3, “Name Dictionary”).
- /////
- //[KeyInfo(KeyType.TextString | KeyType.Optional)]
- //public const string Desc = "/Desc";
+ ///
+ /// (Optional; PDF 1.6) Descriptive text associated with the file specification. It is used for
+ /// files in the EmbeddedFiles name tree (see Section 3.6.3, “Name Dictionary”).
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string Desc = "/Desc";
/////
///// (Optional; must be indirect reference; PDF 1.7) A collection item dictionary, which is used to
@@ -170,6 +300,13 @@ public class Keys : KeysBase
//[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
//public const string CI = "/CI";
+ ///
+ /// (Optional; PDF 2.0) A name value that represents the relationship between the component of this PDF document
+ /// that refers to this file specification and the associated file denoted by this file specification dictionary.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string AFRelationship = "/AFRelationship";
+
// ReSharper restore InconsistentNaming
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs
index 22273bf1..98d2eaf6 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs
@@ -1,16 +1,20 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using System.Collections;
using System.Text;
using PdfSharp.Internal;
-namespace PdfSharp.Pdf.Content.Objects // TODO_OLD: split into single files
+// v7.0.0 TODO
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Content.Objects // TODO: split into single files
{
///
/// Base class for all PDF content stream objects.
///
- public abstract class CObject : ICloneable
+ public abstract class CObject : ICloneable // #FILE: CObject.cs
{
///
/// Initializes a new instance of the class.
@@ -34,11 +38,14 @@ protected CObject()
protected virtual CObject Copy() => (CObject)MemberwiseClone();
///
- ///
+ /// Must be overridden in a derived class and writes the object to a content writer.
///
internal abstract void WriteObject(ContentWriter writer);
}
+}
+namespace PdfSharp.Pdf.Content.Objects // TODO: split into single files
+{
///
/// Represents a comment in a PDF content stream.
///
@@ -63,6 +70,7 @@ public string Text
get => _text ?? "";
set => _text = value;
}
+
string? _text;
///
@@ -72,7 +80,10 @@ public string Text
internal override void WriteObject(ContentWriter writer) => writer.WriteLineRaw(ToString());
}
+}
+namespace PdfSharp.Pdf.Content.Objects
+{
///
/// Represents a sequence of objects in a PDF content stream.
///
@@ -90,7 +101,7 @@ public class CSequence : CObject, IList
protected override CObject Copy()
{
var clone = (CSequence)base.Copy();
- clone._items = [];
+ clone._items.Clear();
for (int idx = 0; idx < _items.Count; idx++)
clone._items.Add(_items[idx].Clone());
return clone;
@@ -181,10 +192,28 @@ public CObject this[int index]
///
/// Converts the sequence to a PDF content stream.
///
- public byte[] ToContent()
+ public string ToContent(ContentWriterOptions? options = null)
{
+#if true
+ options ??= new();
+ //Stream stream = new MemoryStream();
+ ContentWriter writer = new(options);
+ WriteObject(writer);
+ //writer.Close(false);
+ var result = writer.ToString();
+ return result;
+
+ //stream.Position = 0;
+ //int count = (int)stream.Length;
+ //byte[] bytes = new byte[count];
+ //var readBytes = stream.Read(bytes, 0, count);
+ //Debug.Assert(readBytes == count);
+ //stream.Close();
+ //return bytes;
+#else
+ options ??= new();
Stream stream = new MemoryStream();
- ContentWriter writer = new(stream);
+ ContentWriter writer = new(stream, options);
WriteObject(writer);
writer.Close(false);
@@ -195,6 +224,7 @@ public byte[] ToContent()
Debug.Assert(readBytes == count);
stream.Close();
return bytes;
+#endif
}
///
@@ -205,8 +235,12 @@ public override string ToString()
var s = new StringBuilder();
for (int idx = 0; idx < _items.Count; idx++)
+ {
+ // Add spaces except for first item.
+ if (idx > 0)
+ s.Append(' ');
s.Append(_items[idx]);
-
+ }
return s.ToString();
}
@@ -253,8 +287,8 @@ void ICollection.CopyTo(CObject[] array, int arrayIndex)
if (_items.Count > array.Length - arrayIndex)
throw new ArgumentException("The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");
- for (int i = arrayIndex; i < _items.Count; i++)
- array[i] = _items[i];
+ for (int idx = arrayIndex; idx < _items.Count; idx++)
+ array[idx] = _items[idx];
}
int ICollection.Count => _items.Count;
@@ -271,9 +305,12 @@ void ICollection.CopyTo(CObject[] array, int arrayIndex)
#endregion
- List _items = [];
+ readonly List _items = [];
}
+}
+namespace PdfSharp.Pdf.Content.Objects
+{
///
/// Represents the base class for numerical objects in a PDF content stream.
///
@@ -288,14 +325,23 @@ public abstract class CNumber : CObject
///// Implements the copy mechanism of this class.
/////
//protected override CObject Copy() => base.Copy();
+
+ public double DoubleValue => GetDoubleValue();
+
+ protected abstract double GetDoubleValue();
}
+}
+namespace PdfSharp.Pdf.Content.Objects
+{
///
/// Represents an integer value in a PDF content stream.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
public class CInteger : CNumber
{
+ public CInteger(int value) => Value = value;
+
///
/// Creates a new object that is a copy of the current instance.
///
@@ -316,6 +362,11 @@ public int Value
}
int _value;
+ protected override Double GetDoubleValue()
+ {
+ return Value;
+ }
+
///
/// Returns a string that represents the current value.
///
@@ -323,15 +374,21 @@ public override string ToString()
=> _value.ToString(CultureInfo.InvariantCulture);
internal override void WriteObject(ContentWriter writer)
- => writer.WriteRaw(ToString() + " ");
+ //=> writer.WriteRaw(ToString() + " ");
+ => writer.Write(this);
}
+}
+namespace PdfSharp.Pdf.Content.Objects
+{
///
/// Represents a real value in a PDF content stream.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
public class CReal : CNumber
{
+ public CReal(double value) => Value = value;
+
///
/// Creates a new object that is a copy of the current instance.
///
@@ -347,6 +404,11 @@ public class CReal : CNumber
///
public double Value { get; set; }
+ protected override Double GetDoubleValue()
+ {
+ return Value;
+ }
+
///
/// Returns a string that represents the current value.
///
@@ -357,9 +419,13 @@ public override string ToString()
}
internal override void WriteObject(ContentWriter writer)
- => writer.WriteRaw(ToString() + " ");
+ //=> writer.WriteRaw(ToString() + " ");
+ => writer.Write(this);
}
+}
+namespace PdfSharp.Pdf.Content.Objects // #FOLDER Pdf.Content.Objects/enum
+{
///
/// Type of the parsed string.
///
@@ -375,105 +441,96 @@ public enum CStringType
///
HexString,
- ///
- /// The string... TODO_OLD.
- ///
- UnicodeString,
-
- ///
- /// The string... TODO_OLD.
- ///
- UnicodeHexString,
-
///
/// The string is the content of a dictionary.
/// Currently, there is no parser for dictionaries in content streams.
///
Dictionary,
}
+}
+namespace PdfSharp.Pdf.Content.Objects
+{
///
/// Represents a string value in a PDF content stream.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
- public class CString : CObject
+ public class CString(string value, CStringType stringType = CStringType.String) : CObject
{
///
/// Creates a new object that is a copy of the current instance.
///
public new CString Clone() => (CString)Copy();
- /////
- ///// Implements the copy mechanism of this class.
- /////
- //protected override CObject Copy() => base.Copy();
-
///
- /// Gets or sets the value.
+ /// Gets the value as a raw string.
///
public string Value
{
- get => _value ?? "";
- set => _value = value;
- }
- string? _value;
+ get => field ?? "";
+ set => field = value ?? throw new ArgumentNullException(nameof(value));
+ } = value;
///
/// Gets or sets the type of the content string.
///
public CStringType CStringType
{
- get => _cStringType ?? NRT.ThrowOnNull();
- set => _cStringType = value;
- }
- CStringType? _cStringType;
+ get; // => _cStringType ?? NRT.ThrowOnNull();
+ set; // => _cStringType = value;
+ } = stringType;
+ //CStringType? _cStringType;
///
- /// Returns a string that represents the current value.
+ /// Returns a string that represents the current value in a form that can be used in a
+ /// PDF content stream.
///
public override string ToString()
{
- var s = new StringBuilder();
+ var sb = new StringBuilder();
+ int length = Value.Length;
+
switch (CStringType)
{
case CStringType.String:
- s.Append("(");
- int length = _value!.Length; // NRT
- for (int ich = 0; ich < length; ich++)
+ sb.Append("(");
+ for (int idx = 0; idx < length; idx++)
{
- char ch = _value[ich];
+ char ch = Value[idx];
switch (ch)
{
+ // Reference 2.0: Table 3 — Escape sequences in literal strings / Page 25
+
case Chars.LF:
- s.Append(@"\n");
+ sb.Append(@"\n");
break;
case Chars.CR:
- s.Append(@"\r");
+ sb.Append(@"\r");
break;
case Chars.HT:
- s.Append(@"\t");
+ sb.Append(@"\t");
break;
case Chars.BS:
- s.Append(@"\b");
+ sb.Append(@"\b");
break;
case Chars.FF:
- s.Append(@"\f");
+ sb.Append(@"\f");
break;
case Chars.ParenLeft:
- s.Append(@"\(");
+ sb.Append(@"\(");
break;
case Chars.ParenRight:
- s.Append(@"\)");
+ sb.Append(@"\)");
break;
case Chars.BackSlash:
- s.Append(@"\\");
+ sb.Append(@"\\");
break;
default:
@@ -488,37 +545,47 @@ public override string ToString()
}
else
#endif
- s.Append(ch);
+ sb.Append(ch);
break;
}
}
- s.Append(')');
+ sb.Append(')');
break;
case CStringType.HexString:
- throw new NotImplementedException();
-
- case CStringType.UnicodeString:
- throw new NotImplementedException();
+ sb.Append("<");
+ for (int ich = 0; ich < length; ich++)
+ {
+ char ch = Value[ich];
+ byte hi = (byte)((ch >> 4) + '0');
+ byte lo = (byte)((ch & 0xF) + '0');
+ sb.Append((char)(hi < ':' ? hi : hi + ('A' - ':')));
+ sb.Append((char)(lo < ':' ? lo : lo + ('A' - ':')));
+ }
+ sb.Append('>');
+ break;
- case CStringType.UnicodeHexString:
- throw new NotImplementedException();
case CStringType.Dictionary:
- s.Append(_value);
+ sb.Append(Value);
break;
default:
throw new ArgumentOutOfRangeException();
}
- return s.ToString();
+ return sb.ToString();
}
internal override void WriteObject(ContentWriter writer)
{
- writer.WriteRaw(ToString());
+ //writer.WriteRaw(ToString());
+ writer.Write(ToString());
}
}
+}
+
+namespace PdfSharp.Pdf.Content.Objects
+{
///
/// Represents a name in a PDF content stream.
@@ -547,12 +614,14 @@ public class CName : CObject
/////
//protected override CObject Copy() => base.Copy();
+ public Name Value => _name ?? "/";
+
///
/// Gets or sets the name. Names must start with a slash.
///
public string Name
{
- get => _name ?? NRT.ThrowOnNull();
+ get => _name ?? "/";
set
{
if (String.IsNullOrEmpty(value))
@@ -570,35 +639,247 @@ public string Name
public override string? ToString() => _name;
internal override void WriteObject(ContentWriter writer)
- => writer.WriteRaw(ToString() + " ");
+ => writer.Write(this);
}
+}
+namespace PdfSharp.Pdf.Content.Objects
+{
///
/// Represents an array of objects in a PDF content stream.
///
[DebuggerDisplay("(count={" + nameof(Count) + "})")]
- public class CArray : CSequence
+ public class CArray : CObject, IList // Not derived from CSequence.
{
///
/// Creates a new object that is a copy of the current instance.
///
public new CArray Clone() => (CArray)Copy();
- /////
- ///// Implements the copy mechanism of this class.
- /////
- //protected override CObject Copy() => base.Copy();
+ ///
+ /// Implements the copy mechanism of this class.
+ ///
+ protected override CObject Copy()
+ {
+ var clone = (CArray)base.Copy();
+ clone._items.Clear();
+ for (int idx = 0; idx < _items.Count; idx++)
+ clone._items.Add(_items[idx].Clone());
+ return clone;
+ }
///
- /// Returns a string that represents the current value.
+ /// Returns a string that represents the current array.
+ ///
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append('[');
+ bool delimiter = true;
+ for (int idx = 0; idx < _items.Count; idx++)
+ {
+#if true
+ var value = _items[idx].ToString() ?? throw new InvalidOperationException("Sequence element must not be null.");
+ if (!delimiter && !CLexer.IsDelimiter(value[0]))
+ sb.Append(' ');
+ delimiter = CLexer.IsDelimiter(value[^1]);
+#else
+ // Add spaces except for first item.
+ if (idx > 0)
+ sb.Append(' ');
+#endif
+ sb.Append(value);
+ }
+ sb.Append(']');
+
+ return sb.ToString();
+ }
+
+ public void Add(CSequence sequence)
+ {
+ foreach (var value in sequence)
+ Add(value);
+ }
+
+ #region IList Members
+
+ ///
+ /// Adds the specified value add the end of the sequence.
+ ///
+ public void Add(CObject value) => _items.Add(value);
+
+ ///
+ /// Removes all elements from the sequence.
+ ///
+ public void Clear() => _items.Clear();
+
+ ///
+ /// Determines whether the specified value is in the sequence.
+ ///
+ public bool Contains(CObject value) => _items.Contains(value);
+
+ ///
+ /// Returns the index of the specified value in the sequence or -1, if no such value is in the sequence.
+ ///
+ public int IndexOf(CObject value) => _items.IndexOf(value);
+
+ ///
+ /// Inserts the specified value in the sequence.
+ ///
+ public void Insert(int index, CObject value) => _items.Insert(index, value);
+
+ ///
+ /// Removes the specified value from the sequence.
+ ///
+ public bool Remove(CObject value) => _items.Remove(value);
+
+ ///
+ /// Removes the value at the specified index from the sequence.
+ ///
+ public void RemoveAt(int index) => _items.RemoveAt(index);
+
+ ///
+ /// Gets or sets a CObject at the specified index.
+ ///
+ ///
+ public CObject this[int index]
+ {
+ get => _items[index];
+ set => _items[index] = value;
+ }
+ #endregion
+
+ #region ICollection Members
+
+ ///
+ /// Copies the elements of the sequence to the specified array.
+ ///
+ public void CopyTo(CObject[] array, int index) => _items.CopyTo(array, index);
+
+ ///
+ /// Gets the number of elements contained in the sequence.
+ ///
+ public int Count => _items.Count;
+
+ #endregion
+
+ #region IEnumerable Members
+
+ ///
+ /// Returns an enumerator that iterates through the sequence.
+ ///
+ public IEnumerator GetEnumerator() => _items.GetEnumerator();
+
+ #endregion
+
+
+ internal override void WriteObject(ContentWriter writer)
+ //=> writer.WriteRaw(ToString());
+ => writer.Write(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ #region IList Members
+
+ int IList.IndexOf(CObject item) => _items.IndexOf(item);
+
+ void IList.Insert(int index, CObject item) => _items.Insert(index, item);
+
+ void IList.RemoveAt(int index) => _items.RemoveAt(index);
+
+ CObject IList.this[int index]
+ {
+ get => _items[index];
+ set => _items[index] = value;
+ }
+
+ #endregion
+
+ #region ICollection Members
+
+ void ICollection.Add(CObject item) => Add(item);
+
+ void ICollection.Clear() => Clear();
+
+ bool ICollection.Contains(CObject item) => Contains(item);
+
+ void ICollection.CopyTo(CObject[] array, int arrayIndex)
+ {
+ if (array == null!)
+ throw new ArgumentNullException(nameof(array));
+
+ if (arrayIndex < 0)
+ throw new ArgumentOutOfRangeException(nameof(array));
+
+ if (_items.Count > array.Length - arrayIndex)
+ throw new ArgumentException("The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");
+
+ for (int idx = arrayIndex; idx < _items.Count; idx++)
+ array[idx] = _items[idx];
+ }
+
+ int ICollection.Count => _items.Count;
+
+ bool ICollection.IsReadOnly => false;
+
+ bool ICollection.Remove(CObject item) => _items.Remove(item);
+
+ #endregion
+
+ #region IEnumerable Members
+
+ IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
+
+ #endregion
+
+ readonly List _items = [];
+ }
+}
+
+namespace PdfSharp.Pdf.Content.Objects
+{
+ ///
+ /// Represents a literal part a PDF content stream.
+ /// Use for the BI and ID operators.
+ /// They are parsed literally.
+ ///
+ //[DebuggerDisplay("({Name}, operands={Operands.Count})")]
+ //[DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")]
+ //[DebuggerTypeProxy(typeof(COperatorDebuggerDisplay))]
+ public class CLiteral : CObject
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CLiteral(string literal) => Value = literal;
+
+ ///
+ /// Creates a new object that is a copy of the current instance.
+ ///
+ public new COperator Clone() => (COperator)Copy();
+
+ ///
+ /// Gets or sets the byte string.
+ ///
+ public string Value { get; set; }
+
+ ///
+ /// Returns a string that represents the current operator.
///
public override string ToString()
- => "[" + base.ToString() + "]";
+ {
+ return Value;
+ }
internal override void WriteObject(ContentWriter writer)
- => writer.WriteRaw(ToString());
+ {
+ writer.WriteRaw(Value);
+ }
}
+}
+namespace PdfSharp.Pdf.Content.Objects
+{
///
/// Represents an operator a PDF content stream.
///
@@ -627,8 +908,7 @@ public class COperator : CObject
///
/// Gets or sets the name of the operator.
///
- /// The name.
- public virtual string Name => _opCode.Name;
+ public string Name => _opCode.Name;
///
/// Gets or sets the operands.
@@ -672,21 +952,56 @@ public override string ToString()
internal override void WriteObject(ContentWriter writer)
{
- if (_sequence != null)
+ if ((OpCode.Flags & OpCodeFlags.InlineImage) == 0)
{
- int count = _sequence.Count;
- for (int idx = 0; idx < count; idx++)
+ if (_sequence != null)
{
- _sequence[idx].WriteObject(writer);
+ int count = _sequence.Count;
+ for (int idx = 0; idx < count; idx++)
+ {
+ _sequence[idx].WriteObject(writer);
+ }
}
- }
- writer.WriteLineRaw(_opCode.OpCodeName == OpCodeName.Dictionary
+ writer.WriteLineRaw(_opCode.OpCodeName == OpCodeName.Dictionary
? " "
: Name);
+ }
+ else
+ {
+ // Hack for inline images.
+ // Operands of BI and ID are parsed as CLiteral.
+
+ switch (OpCode.OpCodeName)
+ {
+ case OpCodeName.BI:
+ {
+ Debug.Assert(_sequence?.Count == 1);
+ var operand = (CLiteral)_sequence[0];
+ writer.WriteLineRaw($"BI\n{operand.Value}");
+ }
+ break;
+
+ case OpCodeName.ID:
+ {
+ Debug.Assert(_sequence?.Count == 1);
+ var operand = (CLiteral)_sequence[0];
+ writer.WriteLineRaw($"ID {operand.Value}");
+ }
+ break;
+
+ case OpCodeName.EI:
+ writer.WriteLineRaw("EI");
+ break;
+
+ default:
+ Debug.Assert(false);
+ break;
+ }
+ }
}
#region Printing/Debugger display
- /// Function returning string that will be used to display objects value in debugger for this type of objects.
+ /// Function returning string that will be used to display object’s value in debugger for this type of objects.
public static Func debuggerDisplay { get; set; } = o => o.ToString(15);
string DebuggerDisplay => debuggerDisplay(this);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/Operators.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/Operators.cs
index 20a39504..ad358361 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/Operators.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/Operators.cs
@@ -1,7 +1,7 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System.Reflection.Emit;
+// v7.0.0 TODO
namespace PdfSharp.Pdf.Content.Objects
{
@@ -98,7 +98,6 @@ public static COperator OperatorFromName(string name)
///
static OpCodes()
{
-
for (int idx = 0; idx < ops.Length; idx++)
{
var op = ops[idx];
@@ -106,11 +105,13 @@ static OpCodes()
}
}
- private static readonly Dictionary StringToOpCode = [];
+ static readonly Dictionary StringToOpCode = [];
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
+ // TODO: Make all these OpCodes internal/public? Or don’t use them in UpdateTextFieldAppearanceStream().
+
static readonly OpCode Dictionary = new("Dictionary", OpCodeName.Dictionary, -1, "name, dictionary", OpCodeFlags.None,
"E.g.: /Name << ... >>");
@@ -129,13 +130,13 @@ static OpCodes()
static readonly OpCode BDC = new("BDC", OpCodeName.BDC, -1, null, OpCodeFlags.None,
"(PDF 1.2) Begin marked-content sequence with property list");
- static readonly OpCode BI = new("BI", OpCodeName.BI, 0, null, OpCodeFlags.None,
+ static readonly OpCode BI = new("BI", OpCodeName.BI, 1, null, OpCodeFlags.InlineImage,
"Begin inline image object");
- static readonly OpCode BMC = new("BMC", OpCodeName.BMC, 1, null, OpCodeFlags.None,
+ internal static readonly OpCode BMC = new("BMC", OpCodeName.BMC, 1, null, OpCodeFlags.None,
"(PDF 1.2) Begin marked-content sequence");
- static readonly OpCode BT = new("BT", OpCodeName.BT, 0, null, OpCodeFlags.None,
+ internal static readonly OpCode BT = new("BT", OpCodeName.BT, 0, null, OpCodeFlags.None,
"Begin text object");
static readonly OpCode BX = new("BX", OpCodeName.BX, 0, null, OpCodeFlags.None,
@@ -168,13 +169,13 @@ static OpCodes()
static readonly OpCode DP = new("DP", OpCodeName.DP, 2, null, OpCodeFlags.None,
"(PDF 1.2) Define marked-content point with property list");
- static readonly OpCode EI = new("EI", OpCodeName.EI, 0, null, OpCodeFlags.None,
+ static readonly OpCode EI = new("EI", OpCodeName.EI, 0, null, OpCodeFlags.InlineImage,
"End inline image object");
- static readonly OpCode EMC = new("EMC", OpCodeName.EMC, 0, null, OpCodeFlags.None,
+ internal static readonly OpCode EMC = new("EMC", OpCodeName.EMC, 0, null, OpCodeFlags.None,
"(PDF 1.2) End marked-content sequence");
- static readonly OpCode ET = new("ET", OpCodeName.ET, 0, null, OpCodeFlags.None,
+ internal static readonly OpCode ET = new("ET", OpCodeName.ET, 0, null, OpCodeFlags.None,
"End text object");
static readonly OpCode EX = new("EX", OpCodeName.EX, 0, null, OpCodeFlags.None,
@@ -204,7 +205,7 @@ static OpCodes()
static readonly OpCode i = new("i", OpCodeName.i, 1, "setflat", OpCodeFlags.None,
"Set flatness tolerance");
- static readonly OpCode ID = new("ID", OpCodeName.ID, 0, null, OpCodeFlags.None,
+ static readonly OpCode ID = new("ID", OpCodeName.ID, 1, null, OpCodeFlags.InlineImage,
"Begin inline image data");
static readonly OpCode j = new("j", OpCodeName.j, 1, "setlinejoin", OpCodeFlags.None,
@@ -234,10 +235,10 @@ static OpCodes()
static readonly OpCode n = new("n", OpCodeName.n, 0, null, OpCodeFlags.None,
"End path without filling or stroking");
- static readonly OpCode q = new("q", OpCodeName.q, 0, "gsave", OpCodeFlags.None,
+ internal static readonly OpCode q = new("q", OpCodeName.q, 0, "gsave", OpCodeFlags.None,
"Save graphics state");
- static readonly OpCode Q = new("Q", OpCodeName.Q, 0, "grestore", OpCodeFlags.None,
+ internal static readonly OpCode Q = new("Q", OpCodeName.Q, 0, "grestore", OpCodeFlags.None,
"Restore graphics state");
static readonly OpCode re = new("re", OpCodeName.re, 4, null, OpCodeFlags.None,
@@ -270,34 +271,34 @@ static OpCodes()
static readonly OpCode scn = new("scn", OpCodeName.scn, -1, "setcolor", OpCodeFlags.None,
"(PDF 1.2) Set color for non-stroking operations (ICCBased and special color spaces)");
- static readonly OpCode sh = new("sh", OpCodeName.sh, 1, "shfill", OpCodeFlags.None,
+ static readonly OpCode sh = new("sh", OpCodeName.Sh, 1, "shfill", OpCodeFlags.None,
"(PDF 1.3) Paint area defined by shading pattern");
- static readonly OpCode Tx = new("T*", OpCodeName.Tx, 0, null, OpCodeFlags.None,
+ internal static readonly OpCode Tx = new("T*", OpCodeName.Tx, 0, null, OpCodeFlags.None,
"Move to start of next text line");
static readonly OpCode Tc = new("Tc", OpCodeName.Tc, 1, null, OpCodeFlags.None,
"Set character spacing");
- static readonly OpCode Td = new("Td", OpCodeName.Td, 2, null, OpCodeFlags.None,
+ internal static readonly OpCode Td = new("Td", OpCodeName.Td, 2, null, OpCodeFlags.None,
"Move text position");
- static readonly OpCode TD = new("TD", OpCodeName.TD, 2, null, OpCodeFlags.None,
+ internal static readonly OpCode TD = new("TD", OpCodeName.TD, 2, null, OpCodeFlags.None,
"Move text position and set leading");
- static readonly OpCode Tf = new("Tf", OpCodeName.Tf, 2, "selectfont", OpCodeFlags.None,
+ internal static readonly OpCode Tf = new("Tf", OpCodeName.Tf, 2, "selectfont", OpCodeFlags.None,
"Set text font and size");
- static readonly OpCode Tj = new("Tj", OpCodeName.Tj, 1, "show", OpCodeFlags.TextOut,
+ internal static readonly OpCode Tj = new("Tj", OpCodeName.Tj, 1, "show", OpCodeFlags.TextOut,
"Show text");
- static readonly OpCode TJ = new("TJ", OpCodeName.TJ, 1, null, OpCodeFlags.TextOut,
+ internal static readonly OpCode TJ = new("TJ", OpCodeName.TJ, 1, null, OpCodeFlags.TextOut,
"Show text, allowing individual glyph positioning");
static readonly OpCode TL = new("TL", OpCodeName.TL, 1, null, OpCodeFlags.None,
"Set text leading");
- static readonly OpCode Tm = new("Tm", OpCodeName.Tm, 6, null, OpCodeFlags.None,
+ internal static readonly OpCode Tm = new("Tm", OpCodeName.Tm, 6, null, OpCodeFlags.None,
"Set text matrix and text line matrix");
static readonly OpCode Tr = new("Tr", OpCodeName.Tr, 1, null, OpCodeFlags.None,
@@ -327,10 +328,10 @@ static OpCodes()
static readonly OpCode y = new("y", OpCodeName.y, 4, "curveto", OpCodeFlags.None,
"Append curved segment to path (final point replicated)");
- static readonly OpCode QuoteSingle = new("'", OpCodeName.QuoteSingle, 1, null, OpCodeFlags.TextOut,
+ internal static readonly OpCode QuoteSingle = new("'", OpCodeName.QuoteSingle, 1, null, OpCodeFlags.TextOut,
"Move to next line and show text");
- static readonly OpCode QuoteDouble = new("\"", OpCodeName.QuoteDouble, 3, null, OpCodeFlags.TextOut,
+ internal static readonly OpCode QuoteDouble = new("\"", OpCodeName.QuoteDouble, 3, null, OpCodeFlags.TextOut,
"Set word and character spacing, move to next line, and show text");
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeFlags.cs
index 60bced01..01867696 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeFlags.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeFlags.cs
@@ -1,7 +1,7 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
+// v7.0.0 TODO
namespace PdfSharp.Pdf.Content.Objects
{
@@ -20,6 +20,12 @@ public enum OpCodeFlags
///
///
TextOut = 0x0001,
+
+ ///
+ /// BI, ID, or EI.
+ ///
+ InlineImage = 0x0002,
+
//Color, Pattern, Images,...
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeName.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeName.cs
index 333a2a03..053512c9 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeName.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeName.cs
@@ -1,21 +1,34 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-// ReSharper disable InconsistentNaming
+// v7.0.0 TODO
namespace PdfSharp.Pdf.Content.Objects
{
+ /*
+ Zero/non-zero winding and alternate/winding modes both refer to polygon filling rules,
+ with "non-zero" and "winding" being the same rule, while "zero" and "alternate" refer to
+ the same "even-odd" rule. The core difference is that non-zero winding (also called winding mode)
+ counts the direction of path segments, filling any region where the winding number is not zero,
+ whereas alternate filling (also called even-odd) simply counts the number of crossings and fills every other region.
+ non-zero = winding
+ even-odd = alternate
+
+ */
+
///
/// The names of the op-codes.
///
- public enum OpCodeName
+ public enum OpCodeName // TODO sync names with PDFsharp PDF Graphics.
{
+ // ReSharper disable InconsistentNaming
+
///
/// Pseudo op-code for the name of a dictionary.
///
Dictionary, // Name followed by dictionary.
- // I know that this is not useable in VB or other languages with no case sensitivity.
+ // I know that this is not usable in VB or other languages with no case sensitivity.
// Reference: TABLE A.1 PDF content stream operators / Page 985
// Reference 2.0: Table A.1 — PDF content stream operators / Page 844
@@ -23,22 +36,24 @@ public enum OpCodeName
///
/// Close, fill, and stroke path using non-zero winding number rule.
///
- b,
+ b, // CloseFillStrokePathWinding
///
/// Fill and stroke path using non-zero winding number rule.
///
- B,
+ B, // FillStrokePathAlternate
///
/// Close, fill, and stroke path using even-odd rule.
///
- bx, // actually b*
+ bx, // CloseFillStrokePathWinding
+ // actually b*
///
/// Fill and stroke path using even-odd rule.
///
- Bx, // actually B*
+ Bx, // FillStrokeEvenOdd_BStar
+ // actually B*
///
/// (PDF 1.2) Begin marked-content sequence with property list
@@ -283,7 +298,7 @@ public enum OpCodeName
///
/// (PDF 1.3) Paint area defined by shading pattern.
///
- sh,
+ Sh,
///
/// Move to start of next text line.
@@ -354,11 +369,19 @@ public enum OpCodeName
/// Append curved segment to path (initial point replicated).
///
v,
+ ///
+ /// Append curved segment to path (initial point replicated).
+ ///
+ AppendCurvedSegmentToPath = v,
///
/// Set line width.
///
w,
+ ///
+ /// Set line width.
+ ///
+ SetLineWidth = w,
///
/// Set clipping path using non-zero winding number rule.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CLexer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CLexer.cs
index 232e5f12..85102697 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CLexer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CLexer.cs
@@ -1,9 +1,13 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System.Text;
using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
using PdfSharp.Logging;
+using PdfSharp.Pdf.Internal;
+using System.Text;
+
+// v7.0.0 REVIEW
namespace PdfSharp.Pdf.Content
{
@@ -39,7 +43,6 @@ public CLexer(MemoryStream content)
_content = content.ToArray();
ContLength = _content.Length;
}
-
_charIndex = 0;
}
@@ -103,7 +106,8 @@ public CSymbol ScanNextToken()
}
///
- /// Scans a comment line. (Not yet used, comments are skipped by lexer.)
+ /// Scans a comment line.
+ /// Not used, comments are skipped by lexer.
///
public CSymbol ScanComment()
{
@@ -116,36 +120,47 @@ public CSymbol ScanComment()
}
///
- /// Scans the bytes of an inline image.
- /// NYI: Just scans over it.
+ /// Scans the dictionary of BI a CLiteral.
///
- public CSymbol ScanInlineImage()
+ public CSymbol ScanBeginImage()
{
- // TODO_OLD: Implement inline images.
- // Skip this:
- // BI
- // … Key-value pairs …
- // ID
- // … Image data …
- // EI
+ int biIndex = _charIndex;
+ AdjustBackwards(ref biIndex, 'B');
+ biIndex += 3;
bool ascii85 = false;
do
{
ScanNextToken();
- // HACK_OLD: Is image ASCII85 decoded?
+ if (Symbol == CSymbol.Eof)
+ break;
+
if (!ascii85 && Symbol == CSymbol.Name && Token is "/ASCII85Decode" or "/A85")
ascii85 = true;
} while (Symbol != CSymbol.Operator || Token != "ID");
- if (ascii85)
- {
- // Look for '~>' because 'EI' may be part of the encoded image.
- while (_currChar != Chars.EOF && (_currChar != '~' || _nextChar != '>'))
- ScanNextChar();
- if (_currChar == Chars.EOF)
- ContentReaderDiagnostics.HandleUnexpectedCharacter(_currChar);
- }
+ Debug.Assert(Token == "ID");
+ AdjustBackwards(ref _charIndex, 'I');
+ int idIndex = _charIndex - 1;
+ SetPosition(_charIndex);
+
+ var biBytes = _content[biIndex..idIndex];
+ var bi = PdfEncoders.RawEncoding.GetString(biBytes);
+
+ ClearToken();
+ _token.Append(bi);
+
+ return CSymbol.Operator;
+ }
+
+ ///
+ /// Scans the data of ID a CLiteral.
+ ///
+ public CSymbol ScanImageData()
+ {
+ int idIndex = _charIndex;
+ AdjustBackwards(ref idIndex, 'I');
+ idIndex += 3;
// Look for 'EI', as 'EI' may be part of the binary image data here too.
while (_currChar != Chars.EOF)
@@ -163,8 +178,17 @@ public CSymbol ScanInlineImage()
if (_currChar == Chars.EOF)
ContentReaderDiagnostics.HandleUnexpectedCharacter(_currChar);
- // We currently do nothing with inline images.
- return CSymbol.None;
+ AdjustBackwards(ref _charIndex, 'E');
+ int endIndex = _charIndex - 1;
+ SetPosition(_charIndex);
+
+ var idBytes = _content[idIndex..endIndex];
+ var id = PdfEncoders.RawEncoding.GetString(idBytes);
+
+ ClearToken();
+ _token.Append(id);
+
+ return CSymbol.Operator;
}
///
@@ -203,7 +227,7 @@ public CSymbol ScanName()
static char LogError(char ch)
{
- PdfSharpLogHost.Logger.LogError("Illegal character {char} in hex string.", ch);
+ PdfSharpLogHost.Logger.LogError("Illegal character '{char}' in hex string.", ch);
return '\0';
}
}
@@ -304,7 +328,7 @@ protected CSymbol ScanDictionary()
///
public CSymbol ScanNumber()
{
- // Note: This is a copy of Lexer.ScanNumber with minimal changes. Keep both versions in sync as far as possible.
+ // Note that this is a copy of Lexer.ScanNumber with minimal changes. Keep both versions in sync as far as possible.
// Parsing Strategy:
// Most real life numbers in PDF files have less than 19 digits. So we try to parse all digits as 64-bit integers
@@ -486,7 +510,10 @@ public CSymbol ScanOperator()
// Scan token.
while (IsOperatorChar(ch))
ch = AppendAndScanNextChar();
-
+#if DEBUG
+ if (_token.ToString() == "BI")
+ _ = typeof(int);
+#endif
return Symbol = CSymbol.Operator;
}
@@ -579,7 +606,7 @@ public CSymbol ScanLiteralString()
default:
if (Char.IsDigit(ch))
{
- // Octal character code
+ // Octal character code.
int n = ch - '0';
if (Char.IsDigit(_nextChar))
{
@@ -721,7 +748,6 @@ public CSymbol ScanLiteralString()
public CSymbol ScanHexadecimalString()
{
Debug.Assert(_currChar == Chars.Less);
-
ClearToken();
ScanNextChar();
while (true)
@@ -814,6 +840,22 @@ char ScanNextChar()
return _currChar;
}
+ void SetPosition(int charIndex)
+ {
+ _charIndex = charIndex;
+ _currChar = (char)_content[_charIndex++];
+ _nextChar = (char)_content[_charIndex++];
+ }
+
+ ///
+ /// Find index of BI, ID, or EI.
+ ///
+ void AdjustBackwards(ref int index, char stop)
+ {
+ while ((char)_content[index] != stop)
+ index--;
+ }
+
///
/// Resets the current token to the empty string.
///
@@ -836,9 +878,9 @@ char AppendAndScanNextChar()
}
///
- /// If the current character is not a white space, the function immediately returns it.
- /// Otherwise, the PDF cursor is moved forward to the first non-white space or EOF.
- /// White spaces are NUL, HT, LF, FF, CR, and SP.
+ /// If the current character is not a white-space, the function immediately returns it.
+ /// Otherwise, the PDF cursor is moved forward to the first non-white-space or EOF.
+ /// White-spaces are NUL, HT, LF, FF, CR, and SP.
///
public char MoveToNonWhiteSpace()
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CParser.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CParser.cs
index fda05cde..245eaccf 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CParser.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CParser.cs
@@ -5,6 +5,8 @@
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.Content.Objects;
+// v7.0.0 REVIEW
+
namespace PdfSharp.Pdf.Content
{
///
@@ -62,6 +64,16 @@ public CSequence ReadContent()
return sequence;
}
+ ///
+ /// Parses whatever comes until the end of the array is reached.
+ ///
+ void ParseArray(CArray array)
+ {
+ var sequence = new CSequence();
+ ParseObject(sequence, CSymbol.EndArray);
+ array.Add(sequence);
+ }
+
///
/// Parses whatever comes until the specified stop symbol is reached.
///
@@ -82,42 +94,29 @@ void ParseObject(CSequence sequence, CSymbol stop)
break;
case CSymbol.Integer:
- CInteger n = new()
- {
- Value = _lexer.TokenToInteger
- };
+ CInteger n = new(_lexer.TokenToInteger);
_operands.Add(n);
break;
case CSymbol.Real:
- CReal r = new()
- {
- Value = _lexer.TokenToReal
- };
+ CReal r = new(_lexer.TokenToReal);
_operands.Add(r);
break;
case CSymbol.String:
+ s = new(_lexer.Token);
+ _operands.Add(s);
+ break;
+
case CSymbol.HexString:
- case CSymbol.UnicodeString:
- case CSymbol.UnicodeHexString:
- s = new()
- {
- Value = _lexer.Token,
- CStringType = CStringType.String // Must set string type. So far, only CStringType.String is supported in CString.ToString().
- };
+ s = new(_lexer.Token, CStringType.HexString);
_operands.Add(s);
break;
case CSymbol.Dictionary:
- s = new()
- {
- Value = _lexer.Token,
- CStringType = CStringType.Dictionary
- };
+ s = new(_lexer.Token, CStringType.Dictionary);
_operands.Add(s);
op = CreateOperator(OpCodeName.Dictionary);
- //_operands.Clear();
sequence.Add(op);
break;
@@ -131,7 +130,6 @@ void ParseObject(CSequence sequence, CSymbol stop)
case CSymbol.Operator:
op = CreateOperator();
- //_operands.Clear();
sequence.Add(op);
break;
@@ -140,10 +138,10 @@ void ParseObject(CSequence sequence, CSymbol stop)
if (_operands.Count != 0)
ContentReaderDiagnostics.ThrowContentReaderException("Array within array...");
- ParseObject(array, CSymbol.EndArray);
+ ParseArray(array);
array.Add(_operands);
_operands.Clear();
- _operands.Add((CObject)array);
+ _operands.Add(array);
break;
case CSymbol.EndArray:
@@ -174,9 +172,32 @@ COperator CreateOperator(OpCodeName nameop)
COperator CreateOperator(COperator op)
{
- if (op.OpCode.OpCodeName == OpCodeName.BI)
+ // Special handling for inline images.
+ if ((op.OpCode.Flags & OpCodeFlags.InlineImage) != 0)
{
- _lexer.ScanInlineImage();
+ string literal;
+ switch (op.OpCode.OpCodeName)
+ {
+ case OpCodeName.BI:
+ _lexer.ScanBeginImage();
+ literal = _lexer.Token;
+ _operands.Add(new CLiteral(literal));
+ break;
+
+ case OpCodeName.ID:
+ _lexer.ScanImageData();
+ literal = _lexer.Token;
+ _operands.Add(new CLiteral(literal));
+ break;
+
+ case OpCodeName.EI:
+ // Has no operands.
+ break;
+
+ default:
+ Debug.Assert(false);
+ break;
+ }
}
#if DEBUG
if (op.OpCode.Operands != -1 && op.OpCode.Operands != _operands.Count)
@@ -188,8 +209,7 @@ COperator CreateOperator(COperator op)
}
}
#endif
- //if (_operands.Count != 0) // Do not create empty Operands sequence.
- op.Operands.Add(_operands);
+ op.Operands.Add(_operands);
_operands.Clear();
return op;
}
@@ -217,8 +237,8 @@ CSymbol ReadSymbol(CSymbol symbol)
return current;
}
- readonly CSequence _operands = new CSequence();
- PdfPage _page = default!;
+ readonly CSequence _operands = new();
+ PdfPage _page = null!;
readonly CLexer _lexer;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/Chars.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/Chars.cs
index 4c95045a..0e7bcaa5 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/Chars.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/Chars.cs
@@ -1,7 +1,7 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-// ReSharper disable InconsistentNaming
+// v7.0
namespace PdfSharp.Pdf.Content
{
@@ -10,6 +10,8 @@ namespace PdfSharp.Pdf.Content
///
static class Chars
{
+ // ReSharper disable InconsistentNaming
+
public const char EOF = '\uFFFF'; // EOF
public const char NUL = '\0'; // NUL
public const char CR = '\x0D'; // Carriage return, ignored by lexer.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReader.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReader.cs
index 91f6efa8..8399a862 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReader.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReader.cs
@@ -3,8 +3,14 @@
using PdfSharp.Pdf.Content.Objects;
+// v7.0.0 TODO
+
namespace PdfSharp.Pdf.Content
{
+ // TODO: FormsCleanUp: Remove Content and Content.Objects and replace with Content2 and Content2.Objects:
+ // Attention: Content(.Objects) classes ContentReader, ContentReaderException, ContentWriter, OpCodeName, CSequence, CName, CArray, COperator, OpCodes are still in use.
+ // Change the namespace in the using classes and test it before removal.
+
///
/// Represents the functionality for reading PDF content streams.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReaderException.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReaderException.cs
index ad0069a3..824f4c25 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReaderException.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReaderException.cs
@@ -1,6 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+// v7.0.0 TODO
+
namespace PdfSharp.Pdf.Content
{
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs
index a8821dae..171cb53c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs
@@ -1,64 +1,84 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using PdfSharp.Pdf.Internal;
+using System.Text;
+using PdfSharp.Pdf.Content.Objects;
+
+// v7.0.0 REVIEW
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf.Content
{
///
/// Represents a writer for generation of PDF streams.
///
- class ContentWriter
+ public class ContentWriter
{
- public ContentWriter(Stream contentStream)
+ public ContentWriter(ContentWriterOptions options)
{
- _stream = contentStream;
-#if DEBUG
- //layout = PdfWriterLayout.Verbose;
-#endif
+ //_stream = contentStream;
+ _options = options;
}
+ StringBuilder _sb = new StringBuilder();
public void Close(bool closeUnderlyingStream)
{
- if (_stream != null && closeUnderlyingStream)
- {
- _stream.Close();
- _stream = null;
- }
+ //if (_stream != null && closeUnderlyingStream)
+ //{
+ // _stream.Close();
+ // _stream = null;
+ //}
}
public void Close() => Close(true);
- public int Position => (int)(_stream?.Position ?? 0);
+ public void Clear()
+ {
+ _sb.Clear();
+ }
+
+ public override String ToString()
+ {
+ return _sb.ToString();
+ }
- //public PdfWriterLayout Layout
- //{
- // get { return layout; }
- // set { layout = value; }
- //}
- //PdfWriterLayout layout;
+ public void Write(CNumber number)
+ {
+ var s = number.ToString();
+ _sb.Append(s);
+ _sb.Append(' '); // HACK
+ }
- //public PdfWriterOptions Options
- //{
- // get { return options; }
- // set { options = value; }
- //}
- //PdfWriterOptions options;
+ public void Write(string s)
+ {
+ _sb.Append(s);
+ }
- // -----------------------------------------------------------
+ public void Write(CName name)
+ {
+ _sb.Append(name);
+ _sb.Append(' '); // HACK
+ }
- ///
- /// Writes the specified value to the PDF stream.
- ///
- public void Write(bool value)
+ public void Write(CArray array)
{
- //WriteSeparator(CharCat.Character);
- //WriteRaw(value ? bool.TrueString : bool.FalseString);
- //lastCat = CharCat.Character;
+ _sb.Append(array.ToString()); // HACK
}
public void WriteRaw(string rawString)
{
+#if true
+ if (String.IsNullOrEmpty(rawString))
+ return;
+ _sb.Append(rawString);
+
+ //AppendBlank(rawString[0]);
+ //byte[] bytes = PdfEncoders.RawEncoding.GetBytes(rawString);
+ //_stream?.Write(bytes, 0, bytes.Length);
+ // ReSharper disable once UseIndexFromEndExpression
+ _lastCat = GetCategory(rawString[rawString.Length - 1]);
+#else
if (String.IsNullOrEmpty(rawString))
return;
//AppendBlank(rawString[0]);
@@ -66,10 +86,23 @@ public void WriteRaw(string rawString)
_stream?.Write(bytes, 0, bytes.Length);
// ReSharper disable once UseIndexFromEndExpression
_lastCat = GetCategory((char)bytes[bytes.Length - 1]);
+#endif
}
public void WriteLineRaw(string rawString)
{
+#if true
+ if (String.IsNullOrEmpty(rawString))
+ return;
+ _sb.Append(rawString);
+ _sb.Append('\n');
+ //AppendBlank(rawString[0]);
+ //byte[] bytes = PdfEncoders.RawEncoding.GetBytes(rawString);
+ //_stream!.Write(bytes, 0, bytes.Length);
+ //_stream!.Write([(byte)'\n'], 0, 1);
+ //_lastCat = GetCategory((char)bytes[bytes.Length - 1]);
+ _lastCat = GetCategory(rawString[^1]);
+#else
if (String.IsNullOrEmpty(rawString))
return;
//AppendBlank(rawString[0]);
@@ -77,13 +110,21 @@ public void WriteLineRaw(string rawString)
_stream!.Write(bytes, 0, bytes.Length);
_stream!.Write([(byte)'\n'], 0, 1);
_lastCat = GetCategory((char)bytes[bytes.Length - 1]);
+#endif
}
public void WriteRaw(char ch)
{
+#if true
+ Debug.Assert(ch < 256, "Raw character greater than 255 detected.");
+ _sb.Append(ch);
+ //_stream?.WriteByte((byte)ch);
+ _lastCat = GetCategory(ch);
+#else
Debug.Assert(ch < 256, "Raw character greater than 255 detected.");
_stream?.WriteByte((byte)ch);
_lastCat = GetCategory(ch);
+#endif
}
///
@@ -121,7 +162,7 @@ void WriteSeparator(CharCat cat, char ch)
switch (_lastCat)
{
//case CharCat.NewLine:
- // if (this.layout == PdfWriterLayout.Verbose)
+ // if (this.layout == PdfWriterLayout.Ver/bose)
// WriteIndent();
// break;
@@ -129,7 +170,7 @@ void WriteSeparator(CharCat cat, char ch)
break;
//case CharCat.Character:
- // if (this.layout == PdfWriterLayout.Verbose)
+ // if (this.layout == PdfWriterLayout.Ver/bose)
// {
// //if (cat == CharCat.Character || ch == '/')
// this.stream.WriteByte((byte)' ');
@@ -168,14 +209,57 @@ enum CharCat
NewLine,
Character,
Delimiter,
+ IRef
}
CharCat _lastCat;
+ /////
+ ///// Gets the underlying stream.
+ /////
+ //internal Stream Stream => _stream ?? NRT.ThrowOnNull();
+
+ //Stream? _stream = null!;
+ ContentWriterOptions _options;
+ }
+}
+
+namespace PdfSharp.Pdf.Content
+{
+ public class ContentWriterOptions
+ {
+ public ContentWriterLayout Layout { get; set; } =
+#if DEBUG
+ ContentWriterLayout.LineFeed;
+#else
+ ContentWriterLayout.SingleLine;
+#endif
+ }
+}
+
+namespace PdfSharp.Pdf.Content // #FOLDER Pdf.Content.enums
+{
+ ///
+ /// Determines how the PDF output stream is formatted. Even all formats create valid PDF files,
+ /// only Compact or Standard should be used for production purposes.
+ ///
+ public enum ContentWriterLayout
+ {
+ // TODO
+
+ ///
+ /// The content stream is written in one single line.
+ /// This is default in release build.
+ ///
+ SingleLine,
+
///
- /// Gets the underlying stream.
+ /// The content stream is written with a line feed after each operator.
+ /// This is default in debug build.
///
- internal Stream Stream => _stream ?? NRT.ThrowOnNull();
+ LineFeed,
- Stream? _stream = null!;
+ /////
+ /////
+ //Verbose,
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs
index 4c615988..fe708fc4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs
@@ -1,6 +1,8 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+// v7.0.0 TODO
+
namespace PdfSharp.Pdf.Content
{
///
@@ -16,14 +18,14 @@ public enum CSymbol
/*Boolean?,*/
String,
HexString,
- UnicodeString,
- UnicodeHexString,
+ //UnicodeString,
+ //UnicodeHexString,
Name,
Operator,
BeginArray,
EndArray,
// IMPROVE
- // Content dictionary << >> is scanned as string literal.
+ // Content dictionary << … >> is scanned as string literal.
// Scan as an object tree.
Dictionary,
Eof,
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filter.cs
index 69d72034..dc2f15c3 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filter.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filter.cs
@@ -78,7 +78,7 @@ public string DecodeToString(byte[] data)
}
///
- /// Removes all white spaces from the data. The function assumes that the bytes are characters.
+ /// Removes all white-spaces from the data. The function assumes that the bytes are characters.
///
protected byte[] RemoveWhiteSpace(byte[] data)
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs
index 3f76f57e..a32c64ac 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using PdfSharp.Pdf.Advanced;
@@ -168,9 +168,11 @@ public static Filter GetFilter(string filterName)
// Crypt = new Crypt();
// return Crypt;
-
+
case PdfFilterNames.RunLengthDecode:
+ case PdfFilterNames.RunLengthDecodeAbbreviation:
case PdfFilterNames.CcittFaxDecode:
+ case PdfFilterNames.CcittFaxDecodeAbbreviation:
case PdfFilterNames.Jbig2Decode:
case PdfFilterNames.JpxDecode:
case PdfFilterNames.Crypt:
@@ -266,15 +268,21 @@ public static byte[] Decode(byte[] data, PdfItem filterItem, PdfItem? decodeParm
{
byte[]? result = null;
-#if true
- PdfReference.Dereference(ref filterItem);
-#else
- if (filterItem is PdfReference iref)
- {
- Debug.Assert(iref.Value != null, "Indirect /Filter value is null");
- filterItem = iref.Value;
- }
-#endif
+ // #US373.
+// // TODO Make sure Decode is never called with a reference.
+//#if true
+// PdfReference.Dereference(ref filterItem);
+//#else
+// if (filterItem is PdfReference iref)
+// {
+// Debug.Assert(iref.Value != null, "Indirect /Filter value is null");
+// filterItem = iref.Value;
+// }
+//#endif
+
+// // TODO Make sure Decode is never called with a reference.
+// if (decodeParms is not null)
+// PdfReference.Dereference(ref decodeParms);
if (filterItem is PdfName && decodeParms is null or PdfDictionary)
{
@@ -285,7 +293,7 @@ public static byte[] Decode(byte[] data, PdfItem filterItem, PdfItem? decodeParm
else if (filterItem is PdfArray itemArray && decodeParms is null or PdfArray)
{
var decodeArray = decodeParms as PdfArray;
- // Array length of filter and decode parms should match. If they dont, return data unmodified.
+ // Array length of filter and decode parms should match. If they don’t, return data unmodified.
if (decodeArray != null && decodeArray.Elements.Count != itemArray.Elements.Count)
return data;
for (var i = 0; i < itemArray.Elements.Count; i++)
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/FlateDecode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/FlateDecode.cs
index fa2f113f..fa314b14 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/FlateDecode.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/FlateDecode.cs
@@ -2,6 +2,8 @@
// See the LICENSE file in the solution root for more information.
using System.IO.Compression;
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
namespace PdfSharp.Pdf.Filters
{
@@ -127,16 +129,30 @@ public override byte[] Decode(byte[] data, FilterParms? parms)
{
using var msInput = new MemoryStream(data);
using var msOutput = new MemoryStream();
+#if true
+ // CMF (Compression Method Flags) must be deflated.
+ byte byte1 = (byte)msInput.ReadByte();
+ if ((byte1 & 0xF) != 0x8)
+ PdfSharpLogHost.Logger.LogWarning("Cannot decode stream. Compression method must be deflate.");
+
+ // Flags.
+ byte byte2 = (byte)msInput.ReadByte();
+ if ((byte2 & 0x20) != 0)
+ PdfSharpLogHost.Logger.LogWarning("Cannot decode stream. DeflateStream does not support Adler32.");
+#else
// ReSharper disable once RedundantAssignment
var header = new byte[]
{
(byte)msInput.ReadByte(), // CMF (Compression Method and flags)
(byte)msInput.ReadByte() // Flags
};
-#if true
- Debug.Assert((header[0] & 0xF) == 0x8); // Compression method must be deflate.
- Debug.Assert((header[1] & 0x20) == 0); // DeflateStream does not support Adler32.
+
+ if ((header[0] & 0xF) != 0x8)
+ PdfSharpLogHost.Logger.LogWarning("Can’t decode stream: Wrong header: Compression method must be deflate.");
+
+ if ((header[1] & 0x20) != 0)
+ PdfSharpLogHost.Logger.LogWarning("Can’t decode stream: Wrong header: DeflateStream does not support Adler32.");
#endif
using var zip = new DeflateStream(msInput, CompressionMode.Decompress, true);
@@ -149,7 +165,6 @@ public override byte[] Decode(byte[] data, FilterParms? parms)
if (parms?.DecodeParms != null)
return StreamDecoder.Decode(msOutput.GetBuffer(), parms.DecodeParms);
}
-
return msOutput.GetBuffer();
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/!Notes.md b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/!Notes.md
new file mode 100644
index 00000000..1a495d38
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/!Notes.md
@@ -0,0 +1,35 @@
+
+
+
+
+## Classes
+
+* Table 224 — Entries in the interactive form dictionary
+ ** Table 225 — Signature flags
+* Table 226 — Entries common to all field dictionaries
+ ** Table 227 — Field flags common to all field types
+ **PdfAcroFieldFlags**
+ **FileName** als eigene Struct implementieren, analog Name
+* Table 228 — Additional entries common to all fields containing variable text
+* Table 230 — Additional entry specific to check box and radio button fields
+* Table 232 — Additional entry specific to a text field
+* Table 234 — Additional entries specific to a choice field
+* Table 235 — Additional entries specific to a signature field
+* Table 236 — Entries in a signature field lock dictionary
+* Table 237 — Entries in a signature field seed value dictionary
+* Table 238 — Entries in a certificate seed value dictionary
+
+* Table 239 — Additional entries specific to a submit-form action
+* Table 240 — Flags for submit-form actions
+* Table 243 — Additional entries specific to an import-data action
+
+* FDF files???
+
+
+## Fild types
+
+Interactive forms support the following field types:
+• Button fields represent interactive controls on the screen that the user can manipulate with the mouse. They include push-buttons, check boxes, and radio buttons.
+• Text fields are boxes or spaces in which the user can enter text from the keyboard.
+• Choice fields contain several text items, at most one of which may be selected as the field value. They include scrollable list boxes and combo boxes.
+• Signature fields represent digital signatures and optional data for authenticating the name of the signer and the document’s contents.
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/Design.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/Design.cs
new file mode 100644
index 00000000..b5ef944a
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/Design.cs
@@ -0,0 +1,1352 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Drawing;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.IO;
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+// v7.0.0 TODO review, Fields OTT
+
+namespace PdfSharp.Pdf.Forms
+{
+ ///
+ /// Base class for all interactive form (AcroForm) fields.
+ /// Fields are always created as indirect objects.
+ ///
+ public abstract class PdfFormField : PdfDictionary // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.4 Field dictionaries / Page 530
+
+ internal PdfFormField(PdfDocument document)
+ : base(document, true)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfFormField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Gets or sets the name of this field.
+ ///
+ ///
+ /// Returns the value of the /T key.
+ ///
+ public string Name
+ => Elements.GetString(Keys.T);
+
+ public string FullName
+ {
+ get
+ {
+ var fullName = Parent?.FullName;
+ var name = Name;
+
+ // The /T entry is required. Only a pure widget at a leaf doesn’t have one. It is recognized as a
+ // PdfFormFieldWidget, as the widget may also contain field keys. If it doesn’t have a name, simply
+ // return its parent’s FullName.
+ if (String.IsNullOrEmpty(name))
+ return fullName ?? throw new NotImplementedException("Should not occur: fullName is null.");
+
+ fullName = String.IsNullOrEmpty(fullName)
+ ? name
+ : fullName + "." + name;
+
+ return fullName;
+ }
+ }
+
+ public PdfFormField? Parent
+ => Elements.GetDictionary(Keys.Parent);
+
+ ///
+ /// Gets the actual type of the field with inheritance considered.
+ ///
+ public virtual Type FieldType => GetType();
+
+ ///
+ /// Gets or sets the field flags of this instance.
+ ///
+ ///
+ /// Returns the value of the /Ff key.
+ ///
+ internal PdfFormFieldFlags Flags
+ {
+ get
+ {
+ if (Elements.TryGetEnum(Keys.Ff, out var result))
+ return result;
+
+ var parent = Parent;
+ return parent?.Flags ?? 0;
+ }
+ set => Elements.SetIntegerFlag(Keys.Ff, (int)value);
+ }
+
+ ///
+ /// Gets or sets the value of the field, considering inheritance.
+ ///
+ public PdfItem? Value
+ {
+ get => GetElementsValue(Keys.V);
+ set
+ {
+ if (IsReadOnly)
+ throw new InvalidOperationException("The field is read only.");
+
+ if (value == null)
+ {
+ Elements.Remove(Keys.V);
+ Parent?.Value = null;
+ return;
+ }
+
+ if (value is PdfString or PdfName)
+ Elements[Keys.V] = value;
+ else
+ throw new NotImplementedException("Values other than string or name cannot be set.");
+ }
+ }
+
+ ///
+ /// Returns false, if the field shall not be considered a field but simply a widget annotation.
+ ///
+ public bool IsFullyQualifiedField()
+ {
+ // According to Reference 2.0: 12.7.4.2 Field names / Page 532,
+ // a field may have no partial field name (/T). This shall than not be considered a field but simply a widget annotation.
+ // Such a field shall only differ in properties specifying the visual appearance and is only one of possibly more representations of the same underlying field.
+ return !String.IsNullOrEmpty(Name);
+ }
+
+ public bool TestFlag(PdfFormFieldFlags flag)
+ {
+ var result = (Flags & flag) == flag;
+ return result;
+ }
+
+ public void SetFlag(PdfFormFieldFlags flag, bool value)
+ {
+ if (value)
+ SetFlag(flag);
+ else
+ ClearFlag(flag);
+ }
+
+ public void SetFlag(PdfFormFieldFlags flag) => Flags |= flag;
+
+ public void ClearFlag(PdfFormFieldFlags flag) => Flags &= ~flag;
+
+ ///
+ /// Gets or sets a value indicating whether the field flag ReadOnly is set.
+ ///
+ public bool IsReadOnly
+ {
+ get => TestFlag(PdfFormFieldFlags.ReadOnly);
+ set
+ {
+ if (value)
+ SetFlag(PdfFormFieldFlags.ReadOnly);
+ else
+ ClearFlag(PdfFormFieldFlags.ReadOnly);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the field flag NoExport is set.
+ /// If set to true, the field shall not be exported.
+ ///
+ public bool IsNoExport
+ {
+ get => TestFlag(PdfFormFieldFlags.NoExport);
+ set
+ {
+ if (value)
+ SetFlag(PdfFormFieldFlags.NoExport);
+ else
+ ClearFlag(PdfFormFieldFlags.NoExport);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the field flag Required is set.
+ /// If set to true, the field is required.
+ ///
+ public bool IsRequired
+ {
+ get => TestFlag(PdfFormFieldFlags.Required);
+ set
+ {
+ if (value)
+ SetFlag(PdfFormFieldFlags.Required);
+ else
+ ClearFlag(PdfFormFieldFlags.Required);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the field flag MultiSelect is set.
+ /// If set to true, a choice field allows selection of multiple items.
+ ///
+ public bool IsMultiSelect
+ {
+ get => TestFlag(PdfFormFieldFlags.MultiSelect);
+ set
+ {
+ if (value)
+ SetFlag(PdfFormFieldFlags.MultiSelect);
+ else
+ ClearFlag(PdfFormFieldFlags.MultiSelect);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the field flag Multiline is set.
+ /// If set to true, a text field may have multiple lines.
+ ///
+ public bool IsMultiLine
+ {
+ get => TestFlag(PdfFormFieldFlags.Multiline);
+ set
+ {
+ if (value)
+ SetFlag(PdfFormFieldFlags.Multiline);
+ else
+ ClearFlag(PdfFormFieldFlags.Multiline);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the field flag Password is set.
+ /// If set to true, a text field doesn’t show its content as clear text.
+ ///
+ public bool IsPassword
+ {
+ get => TestFlag(PdfFormFieldFlags.Password);
+ set
+ {
+ if (value)
+ SetFlag(PdfFormFieldFlags.Password);
+ else
+ ClearFlag(PdfFormFieldFlags.Password);
+ }
+ }
+
+ ///
+ /// Indicates whether the field has children in the /Kids array.
+ ///
+ public bool HasKids
+ {
+ get
+ {
+ var fields = Elements.GetArray(Keys.Kids);
+ return fields?.Elements.Count > 0;
+ }
+ }
+
+ ///
+ /// Gets all fields within this field.
+ ///
+ /// Returns only fields that shall not be considered as widget annotations only.
+ public IEnumerable GetKids(bool onlyFullyQualifiedFields = false)
+ {
+ if (!HasKids)
+ yield break;
+
+ foreach (var kidField in Kids)
+ {
+ if (onlyFullyQualifiedFields && !kidField.IsFullyQualifiedField())
+ continue;
+
+ yield return kidField;
+ }
+ }
+
+ ///
+ /// Gets all fields within this field recursively.
+ ///
+ /// Returns only fields that shall not be considered as widget annotations only.
+ public IEnumerable GetDescendants(bool onlyFullyQualifiedFields = false)
+ {
+ foreach (var kidField in GetKids(onlyFullyQualifiedFields))
+ {
+ yield return kidField;
+
+ foreach (var descendant in kidField.GetDescendants(onlyFullyQualifiedFields))
+ yield return descendant;
+ }
+ }
+
+ ///
+ /// Gets the collection of fields within this field.
+ ///
+ public PdfFormFields Kids => Elements.GetRequiredArray(Keys.Kids, VCF.Create);
+
+ ///
+ /// Gets a value of a field entry, considering inheritance.
+ ///
+ public T? GetElementsValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key) where T : PdfItem
+ {
+ return GetElementsValue(key, out _);
+ }
+
+ ///
+ /// Gets a value of a field entry, considering inheritance.
+ ///
+ public T? GetElementsValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, out PdfFormField? fieldHoldingValue) where T : PdfItem
+ {
+ // Due to inheritance, the value of an entry can be set at a field, that is not of the type that defines the key.
+ // In some cases it is also important to know, at which field in the hierarchy the value is set.
+
+ var value = Elements.GetValue(key);
+ // Option 1: Value is set at this field.
+ if (value != null)
+ fieldHoldingValue = this;
+ // Option 2: Value may be set at parent field: Recursion.
+ else if (Parent != null)
+ value = Parent.GetElementsValue(key, out fieldHoldingValue);
+ // Option 3: Value is not set.
+ else
+ fieldHoldingValue = null;
+
+ return value;
+ }
+
+ ///
+ /// Sets the value of a field entry, checking the type with the given meta.
+ ///
+ internal void SetElementsValue(DictionaryMeta meta, string key, T value) where T : PdfItem
+ {
+ // Due to inheritance, the value of an entry can be set at a field, that is not of the type that defines the key.
+
+ var descriptor = meta[key];
+ var expectedType = descriptor?.GetValueType();
+ var actualType = typeof(T);
+
+ if (expectedType != null && expectedType.IsAssignableFrom(actualType))
+ throw new Exception($"Value of {key} must be of type {expectedType}. The value of type {actualType} can’t be assigned");
+
+ Elements[key] = value;
+ }
+
+ ///
+ /// Gets a value indicating whether Acro field contains keys of a PdfWidgetAnnotation.
+ ///
+ public bool ContainsWidgetPart // TODO: FormsCleanUp: Remove. Use GetAsWidgetAnnotation() or TryGetAsWidgetAnnotation() instead.
+ {
+ get
+ {
+ // TODO: Test may be too simple.
+ if (Elements.TryGetName("/Subtype", out var value))
+ {
+ return value == "/Widget";
+ }
+ return false;
+ }
+ }
+
+ internal bool TryGetAsWidgetAnnotation([MaybeNullWhen(false)] out PdfWidgetAnnotation result)
+ {
+ if (!PdfWidgetAnnotation.IsWidgetAnnotation(this))
+ {
+ result = _widget = null;
+ return false;
+ }
+
+ result = _widget ??= new(this);
+ return true;
+ }
+
+ internal PdfWidgetAnnotation GetAsWidgetAnnotation()
+ {
+ if (!TryGetAsWidgetAnnotation(out var result))
+ {
+ throw new InvalidOperationException(
+ $"This Acro field is of subtype '{Elements.GetName(PdfAnnotation.Keys.Subtype, false, "/???")}', but it should be of subtype '/Widget'.");
+ }
+ return result;
+ }
+ internal PdfWidgetAnnotation? _widget; // If the object ID of the field changes the widget must be adjusted too.
+
+ internal override void PrepareForSave()
+ {
+ //base.PrepareForSave();
+ if (Elements.TryGetValue(Keys.Ff, out var item))
+ item.IsFlag = true;
+
+ foreach (var field in GetKids())
+ {
+ field.PrepareForSave();
+ }
+ }
+
+ internal override void WriteObject(PdfWriter writer)
+ {
+#if DEBUG
+ if (writer.IsVerboseLayout)
+ {
+ if (PdfWidgetAnnotation.IsWidgetAnnotation(this))
+ writer.WriteComment("This field is also a widget");
+ }
+#endif
+ base.WriteObject(writer);
+ }
+
+ ///
+ /// Gets the derived type of PdfFormField.
+ /// Returns field type and a flag that indicates if the field is also a widget.
+ ///
+ internal static (Type Type, bool IsWidget) GetAcroFieldType(PdfDictionary dict)
+ {
+ bool isWidget = dict.Elements.GetName(PdfAnnotation.Keys.Subtype) == "/Widget";
+ string ft = dict.Elements.GetName(Keys.FT);
+ PdfFormFieldFlags flags = (PdfFormFieldFlags)dict.Elements.GetInteger(Keys.Ff);
+ switch (ft)
+ {
+ case PdfFormFieldType.ButtonLiteral:
+ if ((PdfFormFieldFlags.Pushbutton & flags) != 0)
+ return (typeof(PdfFormPushButtonField), isWidget);
+
+ if ((PdfFormFieldFlags.Radio & flags) != 0)
+ return (typeof(PdfFormRadioButtonField), isWidget);
+
+ return (typeof(PdfFormCheckBoxField), isWidget);
+
+ case PdfFormFieldType.TextLiteral:
+ return (typeof(PdfFormTextField), isWidget);
+
+ case PdfFormFieldType.ChoiceLiteral:
+ return (PdfFormFieldFlags.Combo & flags) == 0
+ ? (typeof(PdfFormListBoxField), isWidget)
+ : (typeof(PdfFormComboBoxField), isWidget);
+
+ case PdfFormFieldType.SignatureLiteral:
+ return (typeof(PdfFormSignatureField), isWidget);
+
+ default:
+ // The field has no field type.
+ if (isWidget)
+ {
+ // A field without field type but widget aspect is a PdfFormFieldWidget.
+ return (typeof(PdfFormFieldWidget), true);
+ }
+
+ // A field without field type and no widget aspect is a PdfFormFieldNode.
+ return (typeof(PdfFormFieldNode), false);
+ }
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : KeysBase
+ {
+ // Reference 2.0: Table 226 — Entries common to all field dictionaries / Page 531
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required for terminal fields; inheritable) The type of field that this dictionary describes:
+ /// Btn Button
+ /// Tx Text
+ /// Ch Choice
+ /// Sig (PDF 1.3) Signature
+ /// This entry may be present in a non-terminal field(one whose descendants are fields) to
+ /// provide an inheritable FT value.However, a non-terminal field does not logically have a
+ /// type of its own; it is merely a container for inheritable attributes that are intended
+ /// for descendant terminal fields of any type.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Required)]
+ public const string FT = "/FT";
+
+ ///
+ /// (Required if this field is the child of another in the field hierarchy; absent otherwise)
+ /// The field that is the immediate parent of this one (the field, if any, whose Kids array
+ /// includes this field). A field can have at most one parent; that is, it can be included in
+ /// the Kids array of at most one other field.
+ ///
+ [KeyInfo(KeyType.Dictionary)]
+ public const string Parent = "/Parent";
+
+ ///
+ /// (Sometimes required, as described below) An array of indirect references to the immediate
+ /// children of this field.
+ /// In a non-terminal field, the Kids array shall refer to field dictionaries that are immediate
+ /// descendants of this field.In a terminal field, the Kids array ordinarily shall refer to one
+ /// or more separate widget annotations that are associated with this field. However, if there is
+ /// only one associated widget annotation, and its contents have been merged into the field
+ /// dictionary, Kids shall be omitted.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional, typeof(PdfFormFields))]
+ public const string Kids = "/Kids";
+
+ ///
+ /// (Required) The partial field name.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string T = "/T";
+
+ ///
+ /// (Optional; PDF 1.3) An alternative field name that shall be used in place of the actual
+ /// field name wherever the field shall be identified in the user interface (such as in error
+ /// or status messages referring to the field). This text is also useful when extracting the
+ /// document’s contents in support of accessibility to users with disabilities or for other
+ /// purposes.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string TU = "/TU";
+
+ ///
+ /// (Optional; PDF 1.3) The mapping name that shall be used when exporting interactive form
+ /// field data from the document.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string TM = "/TM";
+
+ ///
+ /// (Optional; inheritable) A set of flags specifying various characteristics of the field.
+ /// Default value: 0.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string Ff = "/Ff";
+
+ ///
+ /// (Optional; inheritable) The field’s value, whose format varies depending on the field type.
+ /// See the descriptions of individual field types for further information.
+ ///
+ [KeyInfo(KeyType.Various | KeyType.Optional)]
+ public const string V = "/V";
+
+ ///
+ /// (Optional; inheritable) The default value to which the field reverts when a reset-form
+ /// action is executed. The format of this value is the same as that of V.
+ ///
+ [KeyInfo(KeyType.Various | KeyType.Optional)]
+ public const string DV = "/DV";
+
+ ///
+ /// (Optional; PDF 1.2) An additional-actions dictionary defining the field’s behaviour in
+ /// response to various trigger events. This entry has exactly the same meaning as the AA
+ /// entry in an annotation dictionary.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string AA = "/AA";
+
+ // ReSharper restore InconsistentNaming
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ ///
+ /// TODO MaOs4StLa What does that mean: Node interactive form (AcroForm) fields.
+ ///
+ public class PdfFormFieldWidget : PdfFormField // TODO: FormsCleanUp: Move to own file.
+ {
+ // // Reference 2.0: 12.7.4 Field dictionaries / Page 530
+
+ internal PdfFormFieldWidget(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfFormFieldWidget(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Gets the actual type of the field with inheritance considered.
+ ///
+ public override Type FieldType
+ {
+ get
+ {
+ // PdfFormFieldWidget doesn’t define the field type, so we ask the parent.
+ var parent = Parent;
+ if (parent == null)
+ throw new Exception("PdfFormFieldWidget must have a parent, which defines the type of the field.");
+ return parent.FieldType;
+ }
+ }
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public abstract class PdfFormTextFieldBase : PdfFormField // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.4.3 Variable text / Page 533
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormTextFieldBase(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormTextFieldBase(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfFormField.Keys
+ {
+ // Reference 2.0: Table 228 — Additional entries common to all fields containing variable text / Page 533
+
+ // ReSharper disable InconsistentNaming
+
+ /////
+ ///// (Required; inheritable) A resource dictionary containing default resources
+ ///// (such as fonts, patterns, or color spaces) to be used by the appearance stream.
+ ///// At a minimum, this dictionary must contain a Font entry specifying the resource
+ ///// name and font dictionary of the default font for displaying the field’s text.
+ /////
+ //[KeyInfo(KeyType.Dictionary | KeyType.Required)]
+ //public const string DR = "/DR";
+
+ ///
+ /// (Required; inheritable) The default appearance string containing a sequence of valid
+ /// page-content graphics or text state operators that define such properties as the field’s
+ /// text size and colour.
+ ///
+ [KeyInfo(KeyType.String | KeyType.Required)]
+ public const string DA = "/DA";
+
+ ///
+ /// (Optional; inheritable) A code specifying the form of quadding (justification) that shall
+ /// be used in displaying the text:
+ /// 0 Left-justified
+ /// 1 Centred
+ /// 2 Right-justified
+ /// Default value: 0 (left-justified).
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string Q = "/Q";
+
+ ///
+ /// (Optional; PDF 1.5) A default style string, as described in Adobe XML Architecture,
+ /// XML Forms Architecture (XFA) Specification, version 3.3.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string DS = "/DS";
+
+ ///
+ /// (Optional; PDF 1.5) A rich text string, as described in Adobe XML Architecture,
+ /// XML Forms Architecture (XFA) Specification, version 3.3.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string RV = "/RV";
+
+ // ReSharper restore InconsistentNaming
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ internal static class PdfFormFieldType // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5 Field types / Page 535
+ public const string ButtonLiteral = "/Btn";
+ public const string TextLiteral = "/Tx";
+ public const string ChoiceLiteral = "/Ch";
+ public const string SignatureLiteral = "/Sig";
+
+ public static readonly Name Button = new(ButtonLiteral);
+ public static readonly Name Text = new(TextLiteral);
+ public static readonly Name Choice = new(ChoiceLiteral);
+ public static readonly Name Signature = new(SignatureLiteral);
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public abstract class PdfFormButtonField : PdfFormField // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.2 Button fields / Page 535
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormButtonField(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormButtonField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfFormField.Keys.FT, PdfFormFieldType.Button.Value);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfFormField.Keys
+ {
+ // Buttons have no additional entries.
+ }
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormPushButtonField : PdfFormButtonField // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.2.2 Push-buttons / Page 536
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormPushButtonField(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormPushButtonField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ SetFlag(PdfFormFieldFlags.Pushbutton);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfFormField.Keys
+ {
+ // Push-buttons have no additional entries.
+ }
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public abstract class PdfFormCheckBoxOrRadioButtonField : PdfFormButtonField // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.2.3 Check boxes / Page 536
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormCheckBoxOrRadioButtonField(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormCheckBoxOrRadioButtonField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfFormButtonField.Keys
+ {
+ // Reference 2.0: Table 230 — Additional entry specific to check box and radio button fields / Page 537
+
+ ///
+ /// (Optional; inheritable; PDF 1.4) An array containing one entry for each widget annotation
+ /// in the Kids array of the radio button or check box field. Each entry shall be a text string
+ /// representing the on state of the corresponding widget annotation.
+ /// When this entry is present, the names used to represent the on state in the AP dictionary of
+ /// each annotation may use numerical position (starting with 0) of the annotation in the Kids
+ /// array, encoded as a name object (for example: /0, /1). This allows distinguishing between
+ /// the annotations even if two or more of them have the same value in the Opt array.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string Opt = "/Opt";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormCheckBoxField : PdfFormCheckBoxOrRadioButtonField // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.2.3 Check boxes / Page 536
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormCheckBoxField(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormCheckBoxField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ // -- Reference 2.0: Table 229 — Field flags specific to button fields
+ // No flag to set here.
+ }
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormRadioButtonField : PdfFormCheckBoxOrRadioButtonField // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.2.4 Radio buttons / Page 538
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormRadioButtonField(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormRadioButtonField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ // NoToggleToOff should be by default. When leaving it clear, clicking the selected radiobutton leaves no radiobutton of the group selected.
+ SetFlag(PdfFormFieldFlags.Radio | PdfFormFieldFlags.NoToggleToOff);
+ }
+
+ // Check boxes have no additional key entries.
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormTextField : PdfFormTextFieldBase // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.3 Text fields / Page 539
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormTextField(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormTextField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfFormField.Keys.FT, PdfFormFieldType.Text.Value);
+
+ // -- Reference 2.0: Table 231 — Field flags specific to text fields
+ // No flag to set here.
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfFormField.Keys
+ {
+ // Reference 2.0: Table 232 — Additional entry specific to a text field / Page 541
+
+ ///
+ /// (Optional; inheritable) The maximum length of the field’s text, in characters.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string MaxLen = "/MaxLen";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public abstract class PdfFormChoiceField : PdfFormTextFieldBase // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.4 Choice fields / Page 541
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormChoiceField(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormChoiceField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ Elements.SetName(PdfFormField.Keys.FT, PdfFormFieldType.Choice.Value);
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfFormTextFieldBase.Keys
+ {
+ // Reference 2.0: Table 234 — Additional entries specific to a choice field / Page 542
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Optional) An array of options that shall be presented to the user. Each element of the
+ /// array is either a text string representing one of the available options or an array
+ /// consisting of two text strings: the option’s export value and the text that shall be
+ /// displayed as the name of the option.
+ /// If this entry is not present, no choices should be presented to the user.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string Opt = "/Opt";
+
+ ///
+ /// (Optional) For scrollable list boxes, the top index (the index in the Opt array of the
+ /// first option visible in the list).
+ /// Default value: 0.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string TI = "/TI";
+
+ ///
+ /// (Sometimes required, otherwise optional; PDF 1.4) For choice fields that allow multiple
+ /// selection (MultiSelect flag set), an array of integers, sorted in ascending order,
+ /// representing the zero-based indices in the Opt array of the currently selected option
+ /// items. This entry shall be used when two or more elements in the Opt array have different
+ /// names but the same export value or when the value of the choice field is an array. If the
+ /// items identified by this entry differ from those in the V entry of the field dictionary
+ /// (see discussion following this Table), the V entry shall be used.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string I = "/I";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+
+ // ReSharper restore InconsistentNaming
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormListBoxField : PdfFormChoiceField // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.4 Choice fields / Page 541
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormListBoxField(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormListBoxField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ // -- Reference 2.0: Table 233 — Field flags specific to choice fields
+ // No flag to set here.
+ }
+
+ // List boxes have no additional key entries.
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormComboBoxField : PdfFormChoiceField // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.4 Choice fields / Page 541
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormComboBoxField(PdfDocument document)
+ : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormComboBoxField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ void Initialize()
+ {
+ //// -- Reference 2.0: Table 233 — Field flags specific to choice fields
+ //AddFieldFlags(PdfFormFieldFlags.Combo);
+ SetFlag(PdfFormFieldFlags.Combo);
+ }
+
+ // Combo boxes have no additional key entries.
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormSignatureField : PdfFormTextFieldBase // TODO: FormsCleanUp: Move to own file.
+ {
+ // Reference 2.0: 12.7.5.5 Signature fields / Page 543
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormSignatureField(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormSignatureField(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ // New code: Renders appearance when adding the field.
+ internal void RenderAppearance()
+ {
+ if (CustomAppearanceHandler != null!)
+ RenderCustomAppearance();
+ }
+
+ ///
+ /// Handler that creates the visual representation of the digital signature in PDF.
+ ///
+ public IAnnotationAppearanceHandler? CustomAppearanceHandler { get; internal set; }
+
+ ///
+ /// Creates the custom appearance form X object for the annotation that represents
+ /// this acro form text field.
+ ///
+ void RenderCustomAppearance()
+ {
+ var rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect);
+ if (rect == null)
+ return;
+
+ var visible = rect.X1 + rect.X2 + rect.Y1 + rect.Y2 != 0;
+ if (!visible)
+ return;
+
+ if (CustomAppearanceHandler == null)
+ throw new Exception("AppearanceHandler is not set.");
+
+ var form = new XForm(Document, rect.Size);
+ var gfx = XGraphics.FromForm(form);
+
+ CustomAppearanceHandler.DrawAppearance(gfx, rect.ToXRect());
+
+ form.DrawingFinished();
+
+ // Get existing or create new appearance dictionary
+ if (!Elements.TryGetValue(PdfAnnotation.Keys.AP, out var ap))
+ {
+ ap = new PdfDictionary(Document);
+ Elements[PdfAnnotation.Keys.AP] = ap;
+ }
+
+ // Set XRef to normal state
+ ap.Elements["/N"] = form.PdfForm.RequiredReference;
+
+ // PdfRenderer can be null.
+ // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
+ form.PdfRenderer?.Close();
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public new class Keys : PdfFormTextFieldBase.Keys
+ {
+ // Reference 2.0: Table 235 — Additional entries specific to a signature field / Page 544
+
+ ///
+ /// (Optional; shall be an indirect reference; PDF 1.5) A signature field lock dictionary
+ /// that specifies a set of form fields that shall be locked when this signature field is
+ /// signed.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string Lock = "/Lock";
+
+ ///
+ /// (Optional; shall be an indirect reference; PDF 1.5) A seed value dictionary containing
+ /// information that constrains the properties of a signature that is applied to this field
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string SV = "/SV";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormSignatureFieldLock : PdfDictionary // TODO: FormsCleanUp: NOT USED BY NOW! Move to own file.
+ {
+ // Reference 2.0: TODO
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormSignatureFieldLock(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormSignatureFieldLock(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : KeysBase // TODO
+ {
+ // TODO Reference 2.0: Table 237 — Entries in a signature field seed value dictionary / Page 545
+
+ ///
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Type = "/Type";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormSignatureFieldSeedValue : PdfDictionary // TODO: FormsCleanUp: NOT USED BY NOW! Move to own file.
+ {
+ // Reference 2.0: TODO
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormSignatureFieldSeedValue(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormSignatureFieldSeedValue(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : KeysBase // TODO
+ {
+ // TODO Reference 2.0: Table 236 — Entries in a signature field lock dictionary / Page 544
+
+ ///
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Type = "/Type";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
+
+namespace PdfSharp.Pdf.Forms
+{
+ public class PdfFormCertificateSeedValue : PdfDictionary // TODO: FormsCleanUp: NOT USED BY NOW! Move to own file.
+ {
+ // Reference 2.0: TODO
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfFormCertificateSeedValue(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormCertificateSeedValue(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ public class Keys : KeysBase // TODO
+ {
+ // TODO Reference 2.0: Table 236 — Entries in a signature field lock dictionary / Page 544
+
+ ///
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Type = "/Type";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/FieldName.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/FieldName.cs
new file mode 100644
index 00000000..55c8e0cc
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/FieldName.cs
@@ -0,0 +1,113 @@
+//// PDFsharp - A .NET library for processing PDF
+//// See the LICENSE file in the solution root for more information.
+
+//// v7.0.0 TODO review
+
+// #pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+//namespace PdfSharp.Pdf.Forms
+//{
+// public struct FieldName // TODO: FormsCleanUp: Remove.
+// {
+// // Reference 2.0: 12.7.4.2 Field names / Page 532
+
+// ///
+// /// Initializes an empty name "/".
+// ///
+// public FieldName()
+// { }
+
+// public FieldName(string canonicName)
+// {
+// Name = new(canonicName);
+// }
+
+// public FieldName(Name name)
+// {
+// Name = name;
+// }
+
+// public Name Name { get; set; }
+
+// public string Value => Name.Value;
+
+// // TODO: Split etc.
+
+// public FieldName Append(FieldName name)
+// {
+// return Append(name.Value);
+// }
+
+// public FieldName Append(string? name)
+// {
+// if (String.IsNullOrEmpty(name))
+// return this;
+// return new(Value + "/" + name[1..]);
+// }
+
+// /////
+// ///// Ensures that the name is formally correct.
+// ///// It must be a non-empty string starting with a '/'.
+// /////
+// //public static void EnsureName(string name)
+// //{
+// // if (String.IsNullOrEmpty(name))
+// // throw new ArgumentNullException(nameof(name));
+
+// // if (name[0] != '/')
+// // throw new ArgumentException($"Name '{name}' must start with a slash ('/').");
+// //}
+
+// /////
+// ///// Converts a string into an atomic name by adding a '/'
+// ///// as prefix to the string.
+// ///// If the specified string already starts with a '/'
+// ///// no action is taken.
+// /////
+// //public static string MakeName(string name)
+// //{
+// // if (String.IsNullOrEmpty(name))
+// // return "/";
+// // if (name[0] != '/')
+// // return String.Concat('/', name);
+// // return name;
+// //}
+
+// public bool IsEmpty => Name.IsEmpty;
+
+// public bool Equals(FieldName other)
+// => Name.Comparer.Compare(Name, other.Name) == 0;
+
+// public override bool Equals(object? obj)
+// {
+// if (obj is FieldName fieldName)
+// return Name.Comparer.Compare(this.Name, fieldName.Name) == 0;
+// return true;
+// }
+
+// public override int GetHashCode() => Name.GetHashCode();
+
+// public override String ToString()
+// {
+// return Value;
+// }
+
+// ///
+// /// Compares two names.
+// ///
+// public int Compare(FieldName l, FieldName r)
+// => Name.Comparer.Compare(l.Name, r.Name);
+
+// ///
+// /// Determines whether the two field names are equal.
+// ///
+// public static bool operator ==(FieldName l, FieldName r) =>
+// Name.Comparer.Compare(l.Name, r.Name) == 0;
+
+// ///
+// /// Determines whether the two field names are not equal.
+// ///
+// public static bool operator !=(FieldName l, FieldName r) => !(l == r);
+
+// public static readonly FieldName Empty = new();
+// }
+//}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfForm.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfForm.cs
new file mode 100644
index 00000000..90243e48
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfForm.cs
@@ -0,0 +1,130 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// v7.0.0 TODO review, Fields OTT
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Forms
+{
+ public sealed class PdfForm : PdfDictionary
+ {
+ // Reference 2.0: 12.7.3 Interactive form dictionary / Page 529
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal PdfForm(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfForm(PdfDictionary dictionary)
+ : base(dictionary)
+ { }
+
+ ///
+ /// Gets the fields collection of this form.
+ /// It is created if it does not exist.
+ ///
+ public PdfFormFields Fields
+ {
+ get
+ {
+ var fields = Elements.GetRequiredValue(Keys.Fields, VCF.CreateIndirect);
+ return fields;
+ }
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ ///
+ internal class Keys : KeysBase
+ {
+ // Reference 2.0: Table 224 — Entries in the interactive form dictionary / Page 529
+
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// (Required) An array of references to the document’s root fields (those with no ancestors
+ /// in the field hierarchy).
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Required, typeof(PdfFormFields))]
+ public const string Fields = "/Fields";
+
+ ///
+ /// (Optional; deprecated in PDF 2.0) A flag specifying whether to construct appearance
+ /// streams and appearance dictionaries for all widget annotations in the document.
+ /// Default value: false.
+ /// A PDF writer shall include this key, with a value of true, if it has not provided
+ /// appearance streams for all visible widget annotations present in the document.
+ /// NOTE
+ /// Appearance streams are required in PDF 2.0 and later.
+ ///
+ [KeyInfo(KeyType.Boolean | KeyType.Optional)]
+ public const string NeedAppearances = "/NeedAppearances";
+
+ ///
+ /// (Optional; PDF 1.3) A set of flags specifying various document-level characteristics
+ /// related to signature fields.
+ /// Default value: 0.
+ ///
+ [KeyInfo("1.3", KeyType.Integer | KeyType.Optional)]
+ public const string SigFlags = "/SigFlags";
+
+ ///
+ /// (Required if any fields in the document have additional-actions dictionaries containing
+ /// a C entry; PDF 1.3) An array of indirect references to field dictionaries with calculation
+ /// actions, defining the calculation order in which their values will be recalculated when
+ /// the value of any field changes.
+ ///
+ [KeyInfo(KeyType.Array)]
+ public const string CO = "/CO";
+
+ ///
+ /// (Optional) A resource dictionary containing default resources (such as fonts, patterns,
+ /// or colour spaces) that shall be used by form field appearance streams. At a minimum,
+ /// this dictionary shall contain a Font entry specifying the resource name and font
+ /// dictionary of the default font for displaying text.
+ ///
+ [KeyInfo(KeyType.Dictionary | KeyType.Optional)]
+ public const string DR = "/DR";
+
+ ///
+ /// (Optional) A document-wide default value for the DA attribute of variable text fields.
+ ///
+ [KeyInfo(KeyType.String | KeyType.Optional)]
+ public const string DA = "/DA"; // E.g. "/DA (/Helv 0 Tf 0 g)"
+
+ ///
+ /// (Optional) A document-wide default value for the Q attribute of variable text fields.
+ ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)]
+ public const string Q = "/Q";
+
+ ///
+ /// (Optional; deprecated in PDF 2.0) A stream or array containing an XFA resource,
+ /// whose format shall conform to the Data Package (XDP) Specification.
+ ///
+ [KeyInfo(KeyType.StreamOrArray | KeyType.Optional)]
+ public const string XFA = "/XFA";
+
+ // ReSharper restore InconsistentNaming
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormButtonField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormButtonField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCertificateSeedValue.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCertificateSeedValue.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCertificateSeedValue.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxOrRadioButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxOrRadioButtonField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxOrRadioButtonField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormChoiceField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormChoiceField.cs
new file mode 100644
index 00000000..87955eb9
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormChoiceField.cs
@@ -0,0 +1,12 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormComboBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormComboBoxField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormComboBoxField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldNode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldNode.cs
new file mode 100644
index 00000000..abd0e45c
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldNode.cs
@@ -0,0 +1,46 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+ ///
+ /// TODO: Needed?
+ /// Represents an Acro field that has no type entry (/FT) nor has a widget annotation part.
+ ///
+ public class PdfFormFieldNode : PdfFormField
+ {
+ // Reference 2.0: 12.7.4 Field dictionaries / Page 530
+
+ internal PdfFormFieldNode(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected PdfFormFieldNode(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Gets the actual type of the field with inheritance considered.
+ ///
+ public override Type FieldType
+ {
+ get
+ {
+ // PdfFormFieldNode doesn’t define the field type, so we ask the parent.
+ var parent = Parent;
+ if (parent == null)
+ throw new Exception("PdfFormFieldNode must have a parent, which defines the type of the field, or it should not be asked, as a child of the field defines the type.");
+ return parent.FieldType;
+ }
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldType.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldType.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldType.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFields.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFields.cs
new file mode 100644
index 00000000..860d6b8a
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFields.cs
@@ -0,0 +1,283 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+ ///
+ /// Holds a collection of interactive fields (Acro fields).
+ ///
+ public sealed class PdfFormFields : PdfArray, IEnumerable
+ {
+ internal PdfFormFields(PdfDocument document)
+ : base(document)
+ { }
+
+ internal PdfFormFields(PdfArray array)
+ : base(array)
+ { }
+
+ //internal void GetDescendantNames(ref List names, string? partialName) // TODO: FormsCleanUp: Remove. Use FullName for each field instead.
+ //{
+ // int count = Elements.Count;
+ // for (int idx = 0; idx < count; idx++)
+ // {
+ // var field = this[idx];
+ // if (field != null!)
+ // field.GetDescendantNames(ref names, partialName);
+ // }
+ //}
+
+ ///
+ /// Gets a field from the collection. For your convenience an instance of a derived class like
+ /// PdfTextField or PdfCheckBox is returned if PDFsharp can guess the actual type of the dictionary.
+ /// If the actual type cannot be guessed by PDFsharp the function returns an instance
+ /// of PdfGenericField.
+ ///
+ public PdfFormField this[int index]
+ {
+ get
+ {
+#if true
+ var item = Elements.GetRequiredValue(index);
+ return item;
+#else
+ PdfItem item = Elements[index];
+ Debug.Assert(item is PdfReference);
+ PdfDictionary? dict = ((PdfReference)item).Value as PdfDictionary;
+ Debug.Assert(dict != null);
+ PdfFormField? field = dict as PdfFormField;
+ if (field == null && dict != null!)
+ {
+ // Do type transformation
+ field = null; // CreateAcroField(dict); MAKE CODE COMPILE
+ //Elements[index] = field.XRef;
+ }
+
+ return field!; // NRT
+#endif
+ }
+ }
+
+ ///
+ /// Returns an enumerator that iterates through an array of Acro fields.
+ ///
+ public new IEnumerator GetEnumerator()
+ {
+ foreach (var item in Elements)
+ {
+ var field = item;
+ PdfReference.Dereference(ref field);
+ yield return (PdfFormField)field;
+ }
+ }
+
+ ///
+ /// Gets all fields recursively.
+ ///
+ /// Returns only fields that shall not be considered as widget annotations only.
+ public IEnumerable GetAllFields(bool onlyFullyQualifiedFields = true)
+ {
+ foreach (var field in this)
+ {
+ if (onlyFullyQualifiedFields && !field.IsFullyQualifiedField())
+ continue;
+
+ yield return field;
+
+ foreach (var descendant in field.GetDescendants(onlyFullyQualifiedFields))
+ yield return descendant;
+ }
+ }
+
+ internal static class AcroFieldPreparer
+ {
+ public static void PrepareDocument(PdfDocument doc)
+ {
+ CreateAcroFieldObjects(doc);
+ CreateAnnotationObjects(doc);
+ }
+
+ ///
+ /// Creates all acro field objects after reading a PDF document.
+ ///
+ /// The document.
+ static void CreateAcroFieldObjects(PdfDocument doc)
+ {
+ var catalog = doc.Catalog;
+ var acroForm = catalog.GetAcroForm();
+
+ var fields = acroForm?.Elements.GetArray(PdfForm.Keys.Fields);
+ if (fields != null)
+ {
+ var fieldCount = fields.Elements.Count;
+ for (int idx = 0; idx < fieldCount; idx++)
+ {
+ var field = fields.Elements.GetRequiredDictionary(idx);
+ CreateDerivedField(fields, idx);
+
+ var acroField = fields.Elements.GetRequiredDictionary(idx);
+#if DEBUG
+ if (!ReferenceEquals(field, acroField))
+ {
+ if (field.IsIndirect)
+ {
+ Debug.Assert(ReferenceEquals(field.RequiredReference, acroField.RequiredReference));
+ }
+ }
+#endif
+ HandleAP(acroField);
+ HandleKids(acroField);
+ }
+ }
+ }
+
+ static void CreateAnnotationObjects(PdfDocument doc)
+ {
+ var pages = doc.Catalog.Pages;
+ foreach (var page in pages)
+ {
+ var annots = page.Elements.GetArray(PdfPage.Keys.Annots);
+ if (annots != null)
+ {
+ for (int idx = 0; idx < annots.Elements.Count; idx++)
+ {
+ var annot = annots.Elements.GetDictionary(idx);
+ if (annot is PdfWidgetAnnotation widget)
+ {
+ throw new InvalidOperationException("WTF happened???");
+ }
+ else if (annot is PdfFormField field)
+ {
+ if (field.TryGetAsWidgetAnnotation(out var proxyWidget))
+ annots.Elements[idx] = proxyWidget;
+ else
+ {
+ PdfSharpLogHost.Logger.LogWarning("A PdfFormField was found in the /Annots array instead of a PdfWidgetAnnotation. The field is not valid here and will be removed to avoid exceptions.");
+ annots.Elements.RemoveAt(idx--);
+ }
+ }
+ else
+ {
+
+ }
+ }
+ }
+ }
+ }
+
+ static void CreateDerivedField(PdfArray fields, int index)
+ {
+ var field = fields.Elements.GetRequiredDictionary(index);
+
+ var (type, isWidget) = PdfFormField.GetAcroFieldType(field);
+ var newField = (PdfFormField)fields.Elements.GetRequiredDictionary(index, VCF.None, type);
+ if (isWidget)
+ newField.GetAsWidgetAnnotation();
+ }
+
+ // ReSharper disable once InconsistentNaming
+ static void HandleAP(PdfFormField field)
+ {
+ var ap = field.Elements.GetDictionary(PdfAnnotation.Keys.AP);
+ if (ap != null)
+ {
+ var valN = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.N);
+ HandleAPEntry(valN);
+
+ var valR = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.R);
+ HandleAPEntry(valR);
+
+ var valD = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.D);
+ HandleAPEntry(valD);
+ }
+
+ // ReSharper disable once InconsistentNaming
+ void HandleAPEntry(PdfDictionary? streamOrDict)
+ {
+ if (streamOrDict == null)
+ return;
+
+ var stream = streamOrDict.Stream;
+ if (stream is null)
+ {
+ var keys = streamOrDict.Elements.Keys;
+ foreach (var key in keys)
+ {
+ var xo = streamOrDict.Elements.GetDictionary(key);
+ if (xo is PdfFormXObject)
+ continue;
+
+ if (xo != null)
+ {
+ Debug.Assert(xo.IsIndirect);
+#if true
+ var xo2 = streamOrDict.Elements.GetDictionary(key, VCF.None, typeof(PdfFormXObject));
+#else
+ TransformToXObject(xo);
+#endif
+ Debug.Assert(xo.IsDead);
+ }
+ else
+ {
+ Debug.Assert(false);
+ }
+ }
+ }
+ else
+ {
+ Debug.Assert(streamOrDict.IsIndirect);
+ TransformToXObject(streamOrDict);
+ }
+
+ void TransformToXObject(PdfDictionary dict)
+ {
+ if (dict is PdfFormXObject alreadyFormXObject)
+ {
+ _ = typeof(int);
+ }
+ else
+ {
+ dict.Elements.CreateContainer(typeof(PdfFormXObject), dict, true);
+ Debug.Assert(dict.IsDead);
+ }
+ }
+ }
+ }
+
+ static void HandleKids(PdfFormField field)
+ {
+ // Don't use GetKids() here, as elements are pure dictionaries by now.
+ var kids = field.Elements.GetArray(PdfFormField.Keys.Kids);
+ var hasKids = field.HasKids;
+ Debug.Assert(kids == null || (hasKids && kids.Elements.Count > 0) || (!hasKids && kids.Elements.Count == 0));
+ if (kids != null)
+ {
+ // Transform kids to Acro fields.
+ var count = kids.Elements.Count;
+ for (int idx = 0; idx < count; idx++)
+ {
+ CreateDerivedField(kids, idx);
+ }
+
+ for (int idx = 0; idx < count; idx++)
+ {
+ var acroField2 = kids.Elements.GetDictionary(idx);
+ if (acroField2 is not PdfFormField)
+ _ = typeof(int);
+
+ var acroField = kids.Elements.GetRequiredDictionary(idx);
+ HandleAP(acroField);
+ HandleKids(acroField);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormListBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormListBoxField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormListBoxField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormPushButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormPushButtonField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormPushButtonField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormRadioButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormRadioButtonField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormRadioButtonField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldLock.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldLock.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldLock.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldSeedValue.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldSeedValue.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldSeedValue.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextField.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextField.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextFieldBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextFieldBase.cs
new file mode 100644
index 00000000..57cc6da5
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextFieldBase.cs
@@ -0,0 +1,13 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Forms
+{
+
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormFieldFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormFieldFlags.cs
new file mode 100644
index 00000000..85b931eb
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormFieldFlags.cs
@@ -0,0 +1,166 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// v7.0.0 Ready
+
+namespace PdfSharp.Pdf.Forms
+{
+ ///
+ /// Specifies the flags of interactive form (AcroForm) fields.
+ ///
+ [Flags]
+ public enum PdfFormFieldFlags
+ {
+ // Reference 2.0: Table 227 — Field flags common to all field types / Page 532
+
+ // ----- Common to all fields -----------------------------------------------------------------------
+
+ ///
+ /// If set, an interactive PDF processor shall not allow a user to change the value of the
+ /// field. Additionally, any associated widget annotations should not interact with the user;
+ /// that is, they should not respond to mouse clicks nor change their appearance in response
+ /// to mouse motions.
+ /// NOTE: This flag is useful for fields whose values are computed or imported from a database.
+ ///
+ ReadOnly = 1 << (1 - 1),
+
+ ///
+ /// If set, the field shall have a value at the time it is exported by a submit-form action.
+ ///
+ Required = 1 << (2 - 1),
+
+ ///
+ /// If set, the field shall not be exported by a submit-form action.
+ ///
+ NoExport = 1 << (3 - 1),
+
+ // ----- Specific to button fields ------------------------------------------------------------------
+
+ // Reference 2.0: Table 229 — Field flags specific to button fields / Page 535
+
+ ///
+ /// (Radio buttons only) If set, exactly one radio button shall be selected at all times;
+ /// selecting the currently selected button has no effect. If clear, clicking the selected
+ /// button deselects it, leaving no button selected.
+ ///
+ NoToggleToOff = 1 << (15 - 1),
+
+ ///
+ /// If set, the field is a set of radio buttons; if clear, the field is a check box.
+ /// This flag may be set only if the Pushbutton flag is clear.
+ ///
+ Radio = 1 << (16 - 1),
+
+ ///
+ /// If set, the field is a push-button that does not retain a permanent value.
+ ///
+ Pushbutton = 1 << (17 - 1),
+
+ ///
+ /// (PDF 1.5) If set, a group of radio buttons within a radio button field that use the same value
+ /// for the on state will turn on and off in unison; that is if one is checked, they are all checked.
+ /// If clear, the buttons are mutually exclusive (the same behaviour as HTML radio buttons).
+ ///
+ RadiosInUnison = 1 << (26 - 1),
+
+ //// ----- Specific to text fields ------------------------------------------------------------------
+
+ // Reference 2.0: Table 231 — Field flags specific to text fields / Page 539
+
+ ///
+ /// If set, the field may contain multiple lines of text; if clear, the field’s text shall be
+ /// restricted to a single line.
+ ///
+ Multiline = 1 << (13 - 1),
+
+ ///
+ /// If set, the field is intended for entering a secure password that should not be echoed
+ /// visibly to the screen. Characters typed from the keyboard shall instead be echoed in some
+ /// unreadable form, such as asterisks or bullet characters.
+ /// NOTE
+ /// To protect password confidentiality, it is imperative that PDF processors never store
+ /// the value of the text field in the PDF file if this flag is set.
+ ///
+ Password = 1 << (14 - 1),
+
+ ///
+ /// (PDF 1.4) If set, the text entered in the field represents the pathname of a file whose
+ /// contents shall be submitted as the value of the field.
+ ///
+ FileSelect = 1 << (21 - 1),
+
+ ///
+ /// (PDF 1.4) If set, text entered in the field shall not be spell-checked.
+ ///
+ DoNotSpellCheckTextField = 1 << (23 - 1),
+
+ ///
+ /// (PDF 1.4) If set, the field shall not scroll (horizontally for single-line fields, vertically
+ /// for multiple-line fields) to accommodate more text than fits within its annotation rectangle.
+ /// Once the field is full, no further text shall be accepted for interactive form filling;
+ /// for non-interactive form filling, the filler should take care not to add more character than
+ /// will visibly fit in the defined area.
+ ///
+ DoNotScroll = 1 << (24 - 1),
+
+ ///
+ /// (PDF 1.5) May be set only if the MaxLen entry is present in the text field dictionary and if the
+ /// Multiline, Password, and FileSelect flags are clear. If set, the field shall be automatically
+ /// divided into as many equally spaced positions, or combs, as the value of MaxLen, and the text
+ /// is laid out into those combs.
+ ///
+ CombTextField = 1 << (25 - 1),
+
+ ///
+ /// (PDF 1.5) If set, the value of this field shall be a rich text string. If the field has a value,
+ /// the RV entry of the field dictionary shall specify the rich text string.
+ ///
+ RichTextTextField = 1 << (26 - 1),
+
+ // ----- Specific to choice fields ------------------------------------------------------------------
+
+ // Reference 2.0: Table 233 — Field flags specific to choice fields / Page 542
+
+ ///
+ /// If set, the field is a combo box; if clear, the field is a list box.
+ ///
+ Combo = 1 << (18 - 1),
+
+ ///
+ /// If set, the combo box shall include an editable text box as well as a drop-down list; if clear,
+ /// it shall include only a drop-down list. This flag shall be used only if the Combo flag is set.
+ ///
+ Edit = 1 << (19 - 1),
+
+ ///
+ /// If set, the field’s option items shall be sorted alphabetically. This flag is intended for use
+ /// by PDF writers, not by PDF readers. PDF readers shall display the options in the order in which
+ /// they occur in the Opt array.
+ ///
+ Sort = 1 << (20 - 1),
+
+ ///
+ /// (PDF 1.4) If set, more than one of the field’s option items may be selected simultaneously;
+ /// if clear, at most one item shall be selected.
+ ///
+ MultiSelect = 1 << (22 - 1),
+
+ ///
+ /// (PDF 1.4) If set, text entered in the field shall not be spell-checked. This flag shall not be
+ /// used unless the Combo and Edit flags are both set.
+ ///
+ DoNotSpellCheckChoiceField = 1 << (23 - 1),
+
+ ///
+ /// (PDF 1.5) If set, the new value shall be committed as soon as a selection is made (commonly with
+ /// the pointing device). In this case, supplying a value for a field involves three actions:
+ /// selecting the field for fill-in, selecting a choice for the fill-in value, and leaving that field,
+ /// which finalizes or "commits" the data choice and triggers any actions associated with the entry
+ /// or changing of this data. If this flag is on, then processing does not wait for leaving the field
+ /// action to occur, but immediately proceeds to the third step.
+ /// This option enables applications to perform an action once a selection is made, without requiring
+ /// the user to exit the field. If clear, the new value is not committed until the user exits the field.
+ ///
+ CommitOnSelChangeChoiceField = 1 << (27 - 1),
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormSignatureFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormSignatureFlags.cs
new file mode 100644
index 00000000..b8098fe9
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormSignatureFlags.cs
@@ -0,0 +1,34 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// v7.0.0 Ready
+
+namespace PdfSharp.Pdf.Forms
+{
+ ///
+ /// Specifies the flags of interactive form (AcroForm) fields.
+ ///
+ [Flags]
+ public enum PdfFormSignatureFlags
+ {
+ // Reference 2.0: Table 225 — Signature flags / Page 530
+
+ ///
+ /// If set, the document contains at least one signature field. This flag allows an interactive
+ /// PDF processor to enable user interface items (such as menu items or push-buttons) related to
+ /// signature processing without having to scan the entire document for the presence of signature
+ /// fields.
+ ///
+ SignaturesExist = 1 << (1 - 1),
+
+ ///
+ /// If set, the document contains signatures that may be invalidated if the PDF file is saved
+ /// (written) in a way that alters its previous contents, as opposed to an incremental update.
+ /// Merely updating the PDF file by appending new information to the end of the previous version
+ /// is safe. Interactive PDF processors may use this flag to inform a user requesting a full save
+ /// that signatures will be invalidated and require explicit confirmation before continuing with
+ /// the operation.
+ ///
+ AppendOnly = 1 << (2 - 1),
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs
index bd76a0e2..6675ea25 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs
@@ -49,6 +49,7 @@ public static class Chars
/// The horizontal tab character.
///
public const char HT = '\t'; // Horizontal tab
+
///
/// The vertical tab character.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs
index c02e6add..738b49dd 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs
@@ -4,6 +4,7 @@
using System.Text;
using Microsoft.Extensions.Logging;
using PdfSharp.Logging;
+using PdfSharp.Internal;
using PdfSharp.Pdf.Internal;
namespace PdfSharp.Pdf.IO
@@ -44,7 +45,16 @@ public SizeType Position
{
get
{
- Debug.Assert(_pdfStream.Position == _charIndex + 2);
+#if DEBUG
+ // The following assertion may not be true in some rare cases,
+ // e.g. if the PDF document is invalid and the lexer reached
+ // unexpectedly EOF.
+ // See PDFsharp.Tests file "Unexpected_Token_EmptyChar(PageCount).pdf".
+ // This file ends with "startxref‹LF›" not followed by an offset.
+ // In this case _charIndex is identical with stream position.
+ if (_pdfLength > _pdfStream.Position)
+ Debug.Assert(_pdfStream.Position == _charIndex + 2);
+#endif
return _charIndex;
}
set
@@ -134,21 +144,19 @@ public Symbol ScanNextToken(bool testForObjectReference)
case >= 'a' and <= 'z':
return ScanKeyword();
-#if DEBUG
- case 'R':
- Debug.Assert(false, "'R' should not be parsed anymore.");
- // Note: "case 'R':" is not scanned, because it is only used in an object reference.
+ case 'R': // Came here only in invalid PDF files.
+ // Note that "case 'R':" is not scanned, because it is only used in an object reference.
// And object references are now parsed the 'compound symbol' ObjRef.
+ // However, invalid PDF files may have entries in the xref table that points randomly
+ // at a position in the file (e.g. timing.pdf in our test files on our NAS).
+ // In this case we still scan it and crash later.
ScanNextChar(true);
- // The next line only exists for the 'UseOldCode' case in PdfReader.
return Symbol = Symbol.R;
-#endif
case Chars.EOF:
return Symbol = Symbol.Eof;
default:
- Debug.Assert(!Char.IsLetter(ch), "PDFsharp did something wrong. See code below.");
ParserDiagnostics.HandleUnexpectedCharacter(ch, DumpNeighborhoodOfPosition());
return Symbol = Symbol.None;
}
@@ -199,12 +207,20 @@ public Symbol ScanName()
ClearToken();
while (true)
{
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(_currChar);
+#endif
var ch = AppendAndScanNextChar();
if (IsWhiteSpace(ch) || IsDelimiter(ch) || ch == Chars.EOF)
break;
if (ch == '#')
{
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('#');
+ _parsedValue.Append(_currChar);
+ _parsedValue.Append(_nextChar);
+#endif
ScanNextChar(true);
var newChar = (_currChar switch
{
@@ -272,7 +288,7 @@ internal Symbol ScanNumber(bool testForObjectReference)
//
// So we introduced a LongInteger.
- // Note: This is a copy of CLexer.ScanNumber with minimal changes. Keep both versions in sync as far as possible.
+ // Note that this is a copy of CLexer.ScanNumber with minimal changes. Keep both versions in sync as far as possible.
// Update StL: Function is revised for object reference look ahead.
// Parsing Strategy:
@@ -593,6 +609,9 @@ public Symbol ScanStringLiteral()
Debug.Assert(_currChar == Chars.ParenLeft);
ClearToken();
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('(');
+#endif
int parenLevel = 0;
//RetryAfterSkipIllegalCharacter:
char ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n.
@@ -603,10 +622,16 @@ public Symbol ScanStringLiteral()
switch (ch)
{
case '(':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('(');
+#endif
parenLevel++;
break;
case ')':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(')');
+#endif
if (parenLevel == 0)
{
ScanNextChar(false); // The string ended, so ignore \r, \n and \r\n again.
@@ -617,49 +642,82 @@ public Symbol ScanStringLiteral()
case '\\':
{
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('\\');
+#endif
ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n.
switch (ch)
{
case 'n':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('n');
+#endif
ch = Chars.LF;
break;
case 'r':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('r');
+#endif
ch = Chars.CR;
break;
case 't':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('t');
+#endif
ch = Chars.HT;
break;
case 'b':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('b');
+#endif
ch = Chars.BS;
break;
case 'f':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('f');
+#endif
ch = Chars.FF;
break;
case '(':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('(');
+#endif
ch = Chars.ParenLeft;
break;
case ')':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(')');
+#endif
ch = Chars.ParenRight;
break;
case '\\':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('\\');
+#endif
ch = Chars.BackSlash;
break;
// AutoCAD PDFs may contain such strings: (\ )
case ' ':
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(' ');
+#endif
// #PRD Notify about a string with an escaped blank.
ch = ' ';
break;
case Chars.CR:
case Chars.LF:
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(ch);
+#endif
ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n.
continue;
@@ -669,6 +727,9 @@ public Symbol ScanStringLiteral()
//if (Char.IsDigit(ch) && ch is not '8' and not '9') // First octal character.
if (ch is >= '0' and <= '7') // First octal character.
{
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(ch);
+#endif
//// Octal character code.
//if (ch >= '8')
// ParserDiagnostics.HandleUnexpectedCharacter(ch);
@@ -677,6 +738,9 @@ public Symbol ScanStringLiteral()
//if (Char.IsDigit(_nextChar) && _nextChar is not '8' and not '9') // Second octal character.
if (_nextChar is >= '0' and <= '7') // Second octal character.
{
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(_nextChar);
+#endif
ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n.
//if (ch >= '8')
// ParserDiagnostics.HandleUnexpectedCharacter(ch);
@@ -685,6 +749,9 @@ public Symbol ScanStringLiteral()
//if (Char.IsDigit(_nextChar) && _nextChar is not '8' and not '9') // Third octal character.
if (_nextChar is >= '0' and <= '7') // Third octal character.
{
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(_nextChar);
+#endif
ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n.
//if (ch >= '8')
// ParserDiagnostics.HandleUnexpectedCharacter(ch);
@@ -701,8 +768,10 @@ public Symbol ScanStringLiteral()
}
else
{
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append(ch);
+#endif
// PDF 32000: "If the character following the REVERSE SOLIDUS is not one of those shown in Table 3, the REVERSE SOLIDUS shall be ignored."
- // fyi: REVERSE SOLIDUS is a backslash
// What does that mean: "abc\qxyz" is "abcxyz" oder "abcqxyz"?
// Adobe Reader ignores '\', but keeps 'q'. We do the same.
// #PRD Notify about unknown escape character.
@@ -718,28 +787,44 @@ public Symbol ScanStringLiteral()
_token.Append(ch);
ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n.
}
+ // (dummy comment to suppress incorrect warning about label indentation)
End:
return Symbol = Symbol.String;
}
///
- /// Scans a hex encoded literal string, contained between "<" and ">".
- ///
+ /// Scans a hex encoded literal byte string, contained between "<" and ">".
+ ///
public Symbol ScanHexadecimalString()
{
Debug.Assert(_currChar == Chars.Less);
+ // 25-09-16/StL
+ // Now we can handle Panose style codes that may look like this:
+ // < 0 0 2 b 6 6 3 8 4 2 2 4>
+ // This is technically an illegal format, but found by a user in a real PDF file.
+
ClearToken();
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Append('<');
+#endif
ScanNextChar(true);
+ bool tryUsePanoseHack = false;
while (true)
{
MoveToNonWhiteSpace();
+#if DEBUG
+ if (_currChar == '4' && _nextChar == '1')
+ _ = typeof(int);
+#endif
if (_currChar == '>')
{
ScanNextChar(true);
break;
}
+ // IMPROVE Handle EOF correctly. Check if other methods must also handle EOF.
+
var hex = _currChar switch
{
>= '0' and <= '9' => _currChar - '0',
@@ -749,14 +834,49 @@ public Symbol ScanHexadecimalString()
};
ScanNextChar(true);
+
+ // Does the string ends before the 2nd hex value?
if (_currChar == '>')
{
- // Second char is optional in PDF spec.
+ if (tryUsePanoseHack)
+ {
+ // Consider this:
+ // "< 0 0 2 b 6 6 3 8 4 2 2 4>"
+ // Ensure that the last "4" does not become a "40" but a "04".
+
+ // Is it presumably a 12 byte Panose style code?
+ if (_token.Length == 11)
+ {
+ _token.Append((char)hex);
+ goto ScanNextChar;
+ }
+ }
+ // Second char is assumed to be '0' if it does not exist according to the PDF specs.
_token.Append((char)(hex << 4));
+
+ ScanNextChar:
ScanNextChar(true);
break;
}
+ if (_currChar == ' ') // Explicitly not: "if (Lexer.IsDelimiter(_currChar))"
+ {
+ // Obviously a single hex character.
+ // Can occur for Panose style codes.
+ tryUsePanoseHack = true;
+
+ _token.Append((char)hex);
+
+ // Go on with next byte.
+ MoveToNonWhiteSpace();
+ continue;
+ }
+
+ // Some fool may add a line-break between the hex digits.
+ MoveToNonWhiteSpace();
+
+ // IMPROVE Handle EOF correctly.
+
hex = (hex << 4) + _currChar switch
{
>= '0' and <= '9' => _currChar - '0',
@@ -767,12 +887,12 @@ public Symbol ScanHexadecimalString()
_token.Append((char)hex);
ScanNextChar(true);
}
-
return Symbol = Symbol.HexString;
- static char LogError(char ch)
+ char LogError(char ch)
{
PdfSharpLogHost.Logger.LogError("Illegal character {char} in hex string.", ch);
+ DumpNeighborhoodOfPosition( /*SizeType position = -1, bool hex = false, int range = 25*/);
return '\0';
}
}
@@ -889,7 +1009,7 @@ public byte[] ScanStream(SizeType position, int length, out int bytesRead)
return bytes;
}
- // Note: Position += length cannot be used here.
+ // Note that Position += length cannot be used here.
Position = position + length;
return bytes;
}
@@ -971,13 +1091,13 @@ public int DetermineStreamLength(SizeType start, int searchLength, SuppressExcep
var rawString = RandomReadRawString(start, searchLength);
// When we come here, we have either an invalid or no \Length entry.
- // Best we can do is to consider all byte before 'endstream' are part of the stream content.
+ // Best we can do is to consider all bytes before 'endstream' are part of the stream content.
// In case the stream is zipped, this is no problem. In case the stream is encrypted
// it would be a serious problem. But we wait if this really happens.
int idxEndStream = rawString.LastIndexOf("endstream", StringComparison.Ordinal);
if (idxEndStream == -1)
{
- SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength());
+ SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength(start));
return -1;
}
@@ -1054,7 +1174,13 @@ internal char ScanNextChar(bool handleCRLF)
///
/// Resets the current token to the empty string.
///
- void ClearToken() => _token.Clear();
+ void ClearToken()
+ {
+ _token.Clear();
+#if PRESERVE_PARSED_VALUES
+ _parsedValue.Clear();
+#endif
+ }
///
/// Appends current character to the token and
@@ -1070,9 +1196,9 @@ char AppendAndScanNextChar()
}
///
- /// If the current character is not a white space, the function immediately returns it.
- /// Otherwise, the PDF cursor is moved forward to the first non-white space or EOF.
- /// White spaces are NUL, HT, LF, FF, CR, and SP.
+ /// If the current character is not a white-space, the function immediately returns it.
+ /// Otherwise, the PDF cursor is moved forward to the first non-white-space or EOF.
+ /// White-spaces are NUL, HT, LF, FF, CR, and SP.
///
public char MoveToNonWhiteSpace()
{
@@ -1114,7 +1240,7 @@ public string DumpNeighborhoodOfPosition(SizeType position = -1, bool hex = fals
//_pdfStream.Position = 5;
//_pdfStream.Position = _pdfLength - 5;
- // Note: The _pdfStream Position is mostly two bytes/chars behind the Lexer Position,
+ // Note that the _pdfStream Position is mostly two bytes/chars behind the Lexer Position,
// because the stream already has read the current and the next character.
if (position < 0)
position = Position;
@@ -1213,6 +1339,7 @@ public bool TokenToBoolean
}
}
+
///
/// Interprets current token as integer literal.
///
@@ -1306,16 +1433,16 @@ internal static bool IsDelimiter(char ch)
// Reference 2.0: 7.1 Table 2 — Delimiter characters / Page 23
return ch switch
{
- '(' => true,
- ')' => true,
- '<' => true,
- '>' => true,
- '[' => true,
- ']' => true,
- '{' => true,
- '}' => true,
- '/' => true,
- '%' => true,
+ '(' => true, // PDF string delimiter
+ ')' => true, // "
+ '<' => true, // PDF dictionary delimiter
+ '>' => true, // "
+ '[' => true, // PDF array delimiter
+ ']' => true, // "
+ '{' => true, // Type 4 PostScript calculator functions
+ '}' => true, // "
+ '/' => true, // PDF names delimiter
+ '%' => true, // PDF comments delimiter
_ => false
};
}
@@ -1325,10 +1452,14 @@ internal static bool IsDelimiter(char ch)
///
public SizeType PdfLength => _pdfLength;
readonly SizeType _pdfLength;
+
SizeType _charIndex;
char _currChar;
char _nextChar;
readonly StringBuilder _token = new();
+#if PRESERVE_PARSED_VALUES
+ readonly StringBuilder _parsedValue = new();
+#endif
long _tokenAsLong;
double _tokenAsReal;
(int, int) _tokenAsObjectID;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs
index 3bc87391..64fe1c92 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs
@@ -1,23 +1,23 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using Microsoft.Extensions.Logging;
using PdfSharp.Internal;
using PdfSharp.Logging;
using PdfSharp.Pdf.Advanced;
-using Microsoft.Extensions.Logging;
namespace PdfSharp.Pdf.IO
{
/*
- Direct and indirect objects
+ Direct and indirect objects:
- * If a simple object (boolean, integer, number, date, string, rectangle etc.) is referenced indirectly,
+ * If a primitive object (boolean, integer, number, date, string, rectangle etc.) is referenced indirectly,
the parser reads this object immediately and consumes the indirection.
- * If a composite object (dictionary, array etc.) is referenced indirectly, a PdfReference object
+ * If a compound object (dictionary, array etc.) is referenced indirectly, a PdfReference object
is returned.
- * If a composite object is a direct object, no PdfReference is created and the object is
+ * If a compound object is a direct object, no PdfReference is created and the object is
parsed immediately.
* A reference to a non-existing object is specified as legal, therefore null is returned.
@@ -243,13 +243,8 @@ PdfObject ReadObjectInternal(PdfObject? pdfObject, PdfObjectID objectID, bool in
}
break;
- // Acrobat 6 Professional proudly presents: The Null object!
- // Even with a one-digit object number an indirect reference «x 0 R» to this object is
- // one character larger than the direct use of «null». Probable this is the reason why
- // it is true that Acrobat Web Capture 6.0 creates this object, but obviously never
- // creates a reference to it!
case Symbol.Null:
- pdfObject = new PdfNullObject(_document);
+ pdfObject = new PdfNullObject(_document, false);
pdfObject.SetObjectID(objectNumber, generationNumber);
if (!fromObjectStream)
ReadSymbol(Symbol.EndObj);
@@ -257,48 +252,54 @@ PdfObject ReadObjectInternal(PdfObject? pdfObject, PdfObjectID objectID, bool in
// Empty object. Invalid PDF, but we need to handle it. Treat as null object.
case Symbol.EndObj: // #INVALID_PDF
- pdfObject = new PdfNullObject(_document);
+ pdfObject = new PdfNullObject(_document, false);
pdfObject.SetObjectID(objectNumber, generationNumber);
return pdfObject;
case Symbol.Boolean:
- pdfObject = new PdfBooleanObject(_document, String.Compare(_lexer.Token, Boolean.TrueString, StringComparison.OrdinalIgnoreCase) == 0);
+ pdfObject = new PdfBooleanObject(_document, String.Compare(_lexer.Token, Boolean.TrueString, StringComparison.OrdinalIgnoreCase) == 0, false);
pdfObject.SetObjectID(objectNumber, generationNumber);
if (!fromObjectStream)
ReadSymbol(Symbol.EndObj);
return pdfObject;
case Symbol.Integer:
- pdfObject = new PdfIntegerObject(_document, _lexer.TokenToInteger);
+ pdfObject = new PdfIntegerObject(_document, _lexer.TokenToInteger, false);
pdfObject.SetObjectID(objectNumber, generationNumber);
if (!fromObjectStream)
ReadSymbol(Symbol.EndObj);
return pdfObject;
case Symbol.LongInteger:
- pdfObject = new PdfLongIntegerObject(_document, _lexer.TokenToLongInteger);
+ pdfObject = new PdfLongIntegerObject(_document, _lexer.TokenToLongInteger, false);
pdfObject.SetObjectID(objectNumber, generationNumber);
if (!fromObjectStream)
ReadSymbol(Symbol.EndObj);
return pdfObject;
case Symbol.Real:
- pdfObject = new PdfRealObject(_document, _lexer.TokenToReal);
+ pdfObject = new PdfRealObject(_document, _lexer.TokenToReal, false);
pdfObject.SetObjectID(objectNumber, generationNumber);
if (!fromObjectStream)
ReadSymbol(Symbol.EndObj);
return pdfObject;
case Symbol.String:
- case Symbol.HexString:
- pdfObject = new PdfStringObject(_document, _lexer.Token);
+ pdfObject = new PdfStringObject(_document, _lexer.Token, false);
+ pdfObject.SetObjectID(objectNumber, generationNumber);
+ if (!fromObjectStream)
+ ReadSymbol(Symbol.EndObj);
+ return pdfObject;
+
+ case Symbol.HexString: // #HEX_STRING_FIX DELETE
+ pdfObject = new PdfStringObject(_document, _lexer.Token, false) { HexLiteral = true };
pdfObject.SetObjectID(objectNumber, generationNumber);
if (!fromObjectStream)
ReadSymbol(Symbol.EndObj);
return pdfObject;
case Symbol.Name:
- pdfObject = new PdfNameObject(_document, _lexer.Token);
+ pdfObject = new PdfNameObject(_document, _lexer.Token, false);
pdfObject.SetObjectID(objectNumber, generationNumber);
if (!fromObjectStream)
ReadSymbol(Symbol.EndObj);
@@ -362,8 +363,7 @@ void ReadDictionaryStream(PdfDictionary dict, SuppressExceptions? suppressObject
int streamLength = GetStreamLength(dict, suppressObjectOrderExceptions);
if (SuppressExceptions.HasError(suppressObjectOrderExceptions))
return;
- //#warning THHO4STLA What to do if startPosition + streamLength is larger than length of stream? => Better not show "Please send us your PDF file" but another error message.
- // TODO_OLD THHO4STLA What to do if startPosition + streamLength is larger than length of stream? => Better not show "Please send us your PDF file" but another error message.
+ // TODO_OLD What to do if startPosition + streamLength is larger than length of stream? => Better not show "Please send us your PDF file" but another error message.
int retryCount = 0;
RetryReadStream:
// Step 3: We try to read the stream content.
@@ -421,7 +421,8 @@ void ReadDictionaryStream(PdfDictionary dict, SuppressExceptions? suppressObject
///
int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderExceptions)
{
- if (dict.Elements["/F"] != null)
+ //if (dict.Elements["/F"] != null) // TODO #US373 Just a null check.
+ if (dict.Elements.HasValue("/F")) // #US373
throw new NotImplementedException("File streams are not yet implemented.");
#if TEST_CODE_
// By uncommenting this and the label below,
@@ -434,7 +435,7 @@ int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderE
// the length of a xref stream.
// Creating object streams requires a sophisticated producer apps. For such apps it is very
// unlikely that they produce ill formatted stream objects.
- // Note: When the stream length is determined by the position of 'endstream' all trailing
+ // When the stream length is determined by the position of 'endstream' all trailing
// CR and LF characters are considered to be part of the stream. In case the stream is
// encrypted decryption will fail.
if (dict is not PdfCrossReferenceStream && dict.Owner.SecuritySettings.IsEncrypted is false)
@@ -442,7 +443,7 @@ int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderE
Debug.Assert(dict.Elements["/Type"]?.ToString() == "/XRef");
#endif
// Most common case first: Length is a direct integer.
- var lengthItem = dict.Elements["/Length"];
+ var lengthItem = dict.Elements["/Length"]; // #US373 References handled below. Should we try GetValue here?
if (lengthItem is PdfInteger pdfInteger)
{
Debug.Assert(Convert.ToInt32(lengthItem) == pdfInteger.Value);
@@ -458,7 +459,7 @@ int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderE
// If somebody came here, please send us your PDF file so that we can fix it (issues (at) pdfsharp.net).
if (reference.Value is not PdfIntegerObject pdfIntegerObject)
{
- SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength());
+ SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength(_lexer.Position));
return -1;
}
@@ -479,7 +480,7 @@ int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderE
catch (Exception ex)
{
// If somebody came here, please send us your PDF file so that we can fix it (issues (at) pdfsharp.net).
- throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength(ex);
+ throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength(_lexer.Position, ex);
}
RestoreState(state);
}
@@ -584,7 +585,7 @@ public PdfArray ReadArray(PdfArray array, bool includeReferences)
Debug.Assert(Symbol == Symbol.BeginArray);
if (array == null!)
- array = new PdfArray(_document);
+ array = new(_document);
var items = ParseObject(Symbol.EndArray);
var count = items.Count;
@@ -628,6 +629,11 @@ internal PdfDictionary ReadDictionary(PdfDictionary? dict, bool includeReference
var items = ParseObject(Symbol.EndDictionary);
int count = items.Count;
+ if (count % 2 != 0)
+ ParserDiagnostics.ThrowParserException(
+ "A PDF dictionary contains an odd number of PDF objects. " +
+ "PDF expects pairs of names and values.");
+
for (int idx = 0; idx < count; idx += 2)
{
var val = items[idx];
@@ -635,6 +641,7 @@ internal PdfDictionary ReadDictionary(PdfDictionary? dict, bool includeReference
ParserDiagnostics.ThrowParserException("Name expected."); // TODO_OLD L10N using PsMsgs
string key = val.ToString() ?? NRT.ThrowOnNull();
+
val = items[idx + 1];
if (includeReferences && val is PdfReference reference)
{
@@ -700,7 +707,7 @@ List ParseObject(Symbol stopSymbol)
break;
case Symbol.HexString:
- items.Add(new PdfString(_lexer.Token, PdfStringFlags.HexLiteral));
+ items.Add(new PdfString(_lexer.Token, true)); // #HEX_STRING_FIX DELETE
break;
case Symbol.Name:
@@ -830,8 +837,7 @@ void ReadObjectID(PdfObject? obj)
int objectNumber = ReadInteger();
int generationNumber = ReadInteger();
ReadSymbol(Symbol.Obj);
- if (obj != null)
- obj.SetObjectID(objectNumber, generationNumber);
+ obj?.SetObjectID(objectNumber, generationNumber);
}
PdfItem ReadReference(PdfReference iref, bool includeReferences)
@@ -1055,10 +1061,6 @@ internal void ReadAllIndirectObjects()
{
if (pdfReference.Value == null!)
{
-#if DEBUG_
- if (pdfReference.ObjectNumber == 25)
- _ = typeof(int);
-#endif
var pdfObject = ReadIndirectObject(pdfReference, null);
Debug.Assert(pdfObject.Reference == pdfReference);
@@ -1161,10 +1163,10 @@ internal void ReadAllObjectStreamsAndTheirReferences()
}
// Create the parser for the object stream.
- var objectStreamParser = new Parser(_document, new MemoryStream(objectStream.Stream.UnfilteredValue), _documentParser);
+ var objectStreamParser = new Parser(_document, new MemoryStream(objectStream.Stream!.UnfilteredValue), _documentParser);
- // Read and add all References to objects residing in the object stream and get all ObjectIDs and offsets .
- var objectIDsWithOffset = objectStream.ReadReferencesAndOffsets(_document.IrefTable);
+ // Get all ObjectIDs and offsets of objects residing in the object stream.
+ var objectIDsWithOffset = objectStream.ReadObjectIDsWithOffsets();
// Save all ObjectIDs with the parser of its ObjectStream and its offset.
foreach (var objectIDWithOffset in objectIDsWithOffset)
@@ -1179,6 +1181,15 @@ internal void ReadAllObjectStreamsAndTheirReferences()
{
// Add object with new objectID.
_objectStreamObjectSources.Add(objectID, (objectStreamParser, offset));
+
+ // Create and add reference to the object if not existing yet.
+ if (!_document.IrefTable.Contains(objectID))
+ {
+ // -1 indicates compressed object.
+ var iref = PdfReference.CreateForObjectID(objectID, -1);
+
+ _document.IrefTable.Add(iref);
+ }
}
else
{
@@ -1336,7 +1347,7 @@ internal PdfTrailer ReadTrailer()
// Implementation note 18 Appendix H:
// Acrobat viewers require only that the %%EOF marker appear somewhere within the last 1024 bytes of the file.
int idx;
- if (length < 1030)
+ if (length <= 1030)
{
// Reading the final 30 bytes should work for all files. But often it does not.
string trail = _lexer.ScanRawString(length - 31, 30); //lexer.Pdf.Substring(length - 30);
@@ -1457,8 +1468,9 @@ internal PdfTrailer ReadTrailer()
idToUse = idChecked;
//ParserDiagnostics.ThrowParserException("Invalid entry in XRef table, ID=" + id + ", Generation=" + generation + ", Position=" + position + ", ID of referenced object=" + idChecked + ", Generation of referenced object=" + generationChecked); // TODO_OLD L10N using PsMsgs
}
- var message = Invariant(
- $"Object ID mismatch: Object at position {position} has ID '{id}' according to xref table and ID '{idChecked}' at its position of file.");
+ var message = id == idChecked ?
+ Invariant($"Object ID/generation mismatch: Object at position {position} has ID '{id} {generation}' according to xref table and ID '{idChecked} {generationChecked}' at its position of file.") :
+ Invariant($"Object ID mismatch: Object at position {position} has ID '{id}' according to xref table and ID '{idChecked}' at its position of file.");
PdfSharpLogHost.Logger.LogError(message);
}
#endif
@@ -1509,6 +1521,11 @@ internal PdfTrailer ReadTrailer()
/// The generation found in the PDF file.
bool CheckXRefTableEntry(SizeType position, int id, int generation, out int idChecked, out int generationChecked)
{
+ // We found a lot of PDF files with wrong position in xref table:
+ // …〈LF〉42 0 obj〈LF〉…
+ // ^
+ // The offsets points behind the numbers.
+ // Should we handle this wrong case?
SizeType origin = _lexer.Position;
idChecked = -1;
generationChecked = -1;
@@ -1550,7 +1567,6 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable)
// Read cross-reference stream.
//Debug.Assert(_lexer.Symbol == Symbol.Integer);
- // NEEDED???
var xrefStart = _lexer.Position - _lexer.Token.Length;
int number = _lexer.TokenToInteger;
@@ -1560,7 +1576,9 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable)
if (generation != 0)
{
// Considered to be an error, but without consequences.
- PdfSharpLogHost.Logger.LogError($"Generation number of object '{number} {generation}' which is cross-reference stream shall not be other than zero.");
+ PdfSharpLogHost.Logger.LogError(
+ $"Generation number of object '{number} {generation}' " +
+ "which is cross-reference stream shall not be other than zero.");
}
// Reference 2.0: 7.5.7 Object streams / Page 61
@@ -1612,7 +1630,14 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable)
//_ = typeof(int);
byte[] bytes = xrefStream.Stream.UnfilteredValue;
- int size = xrefStream.Elements.GetInteger(PdfCrossReferenceStream.Keys.Size);
+ // We found PDF files with invalid size entry.
+ int size = xrefStream.Elements.GetInteger(PdfCrossReferenceStream.Keys.Size, false, Int32.MaxValue);
+ if (size == Int32.MaxValue)
+ {
+ var value = xrefStream.Elements.GetValue(PdfCrossReferenceStream.Keys.Size, VCF.NoTransform)?.ToString() ?? "(no value)";
+ throw new InvalidOperationException(
+ $"The /Size entry in a cross-reference stream has the invalid value '{value}'.");
+ }
var index = xrefStream.Elements.GetValue(PdfCrossReferenceStream.Keys.Index) as PdfArray;
int prev = xrefStream.Elements.GetInteger(PdfCrossReferenceStream.Keys.Prev);
var w = (PdfArray?)xrefStream.Elements.GetValue(PdfCrossReferenceStream.Keys.W);
@@ -1621,7 +1646,7 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable)
// Setup subsections.
int subsectionCount;
- int[][] subsections = default!;
+ int[][] subsections = null!;
int subsectionEntryCount = 0;
if (index == null)
{
@@ -1720,7 +1745,13 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable)
if (objectID.ObjectNumber == 1074)
_ = typeof(int);
#endif
- Debug.Assert(objectID.GenerationNumber == item.Field3);
+ // TODO Sometimes fails.
+ //Debug.Assert(objectID.GenerationNumber == item.Field3);
+
+#if DEBUG
+ if (objectID.ObjectNumber == 96049)
+ _ = typeof(int);
+#endif
// Ignore the latter one.
if (!xrefTable.Contains(objectID))
@@ -1743,72 +1774,153 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable)
}
}
}
+
+ // If set, replace the temporary PdfReferences in xrefStream.Elements, which were set when reading the XrefStream’s dictionary.
+ // They have no position set, so reading them would assign the wrong objects to their PdfReference duplicates.
+ // The actual references with the correct positions were read later to the xrefTable when reading the XrefStream’s stream.
+ // These are the ones to replace the temporary ones.
+ ReplaceTemporaryReferences(xrefStream, xrefTable);
+
return xrefStream;
}
+ void ReplaceTemporaryReferences(PdfArray array, PdfCrossReferenceTable xrefTable)
+ {
+ for (var i = 0; i < array.Elements.Count; i++)
+ {
+ switch (array.Elements[i])
+ {
+ case PdfReference reference:
+ {
+ var refID = reference.ObjectID;
+ var actualReference = xrefTable[refID];
+
+ if (reference != actualReference && actualReference != null)
+ array.Elements[i] = actualReference;
+ break;
+ }
+
+ case PdfDictionary dic:
+ ReplaceTemporaryReferences(dic, xrefTable);
+ break;
+
+ case PdfArray arr:
+ ReplaceTemporaryReferences(arr, xrefTable);
+ break;
+ }
+ }
+ }
+
+ void ReplaceTemporaryReferences(PdfDictionary dictionary, PdfCrossReferenceTable xrefTable)
+ {
+ // Dictionary elements are modified inside the loop. Avoid "Collection was modified; enumeration operation may not execute" error occuring in net 4.6.2.
+ // There is no way to access KeyValuePairs via index natively to use a for loop with.
+ // Instead, enumerate Keys and get value via Elements[key], which should be O(1).
+ foreach (var key in dictionary.Elements.Keys)
+ {
+ var value = dictionary.Elements[key]; // #US373 References handled below.
+
+ switch (value)
+ {
+ case PdfReference reference:
+ {
+ var refID = reference.ObjectID;
+ var actualReference = xrefTable[refID];
+
+ if (reference != actualReference && actualReference != null)
+ dictionary.Elements[key] = actualReference;
+ break;
+ }
+
+ case PdfDictionary dict:
+ ReplaceTemporaryReferences(dict, xrefTable);
+ break;
+
+ case PdfArray arr:
+ ReplaceTemporaryReferences(arr, xrefTable);
+ break;
+ }
+ }
+ }
+
///
/// Parses a PDF date string.
///
- internal static DateTime ParseDateTime(string date, DateTime errorValue) // TODO_OLD: TryParseDateTime
+ internal static bool TryParseDate(string pdfDate, out DateTimeOffset? dateTime)
{
- DateTime datetime = errorValue;
+ dateTime = null;
+ bool success = false;
try
{
- if (date.StartsWith("D:", StringComparison.Ordinal))
+ TryAgain:
+ if (pdfDate.StartsWith("D:", StringComparison.Ordinal))
{
// Format is
// D:YYYYMMDDHHmmSSOHH'mm'
// ^2 ^10 ^16 ^20
- int length = date.Length;
+ // with O is '+', '-', or 'Z'.
+ int length = pdfDate.Length;
int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, hh = 0, mm = 0;
char o = 'Z';
- if (length >= 10)
+ if (length >= 10) // Can read date?
{
- year = Int32.Parse(date.Substring(2, 4));
- month = Int32.Parse(date.Substring(6, 2));
- day = Int32.Parse(date.Substring(8, 2));
- if (length >= 16)
+ year = Int32.Parse(pdfDate.Substring(2, 4));
+ month = Int32.Parse(pdfDate.Substring(6, 2));
+ day = Int32.Parse(pdfDate.Substring(8, 2));
+ if (length >= 16) // Can read time?
{
- hour = Int32.Parse(date.Substring(10, 2));
- minute = Int32.Parse(date.Substring(12, 2));
- second = Int32.Parse(date.Substring(14, 2));
- if (length >= 23)
+ hour = Int32.Parse(pdfDate.Substring(10, 2));
+ minute = Int32.Parse(pdfDate.Substring(12, 2));
+ second = Int32.Parse(pdfDate.Substring(14, 2));
+ if (length >= 22) // Can read offset? Do not care about trailing ‘'’.
{
- if ((o = date[16]) != 'Z')
+ if ((o = pdfDate[16]) != 'Z')
{
- hh = Int32.Parse(date.Substring(17, 2));
- mm = Int32.Parse(date.Substring(20, 2));
+ hh = Int32.Parse(pdfDate.Substring(17, 2));
+ mm = Int32.Parse(pdfDate.Substring(20, 2));
}
}
}
}
- // There are miserable PDF tools around the world.
- month = Math.Min(Math.Max(month, 1), 12);
- datetime = new DateTime(year, month, day, hour, minute, second);
+
+ var offset = TimeSpan.Zero;
if (o != 'Z')
{
- TimeSpan ts = new TimeSpan(hh, mm, 0);
+ offset = new TimeSpan(hh, mm, 0);
if (o == '-')
- datetime = datetime.Add(ts);
- else
- datetime = datetime.Subtract(ts);
+ offset = offset.Negate();
}
- // Now that we converted datetime to UTC, mark it as UTC.
- datetime = DateTime.SpecifyKind(datetime, DateTimeKind.Utc);
+
+ // There are miserable PDF tools around the world.
+ month = Math.Min(Math.Max(month, 1), 12);
+
+ dateTime = new(year, month, day, hour, minute, second, offset);
+ success = true;
}
else
{
- // Some libraries use plain English format.
- datetime = DateTime.Parse(date, CultureInfo.InvariantCulture);
+ // Some dumb PDF creator tools use plain English date format.
+ if (DateTimeOffset.TryParse(pdfDate, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
+ {
+ dateTime = result;
+ success = true;
+ }
+ else
+ {
+ // Try again with starting “D:”. The specs say it is optional.
+ pdfDate = "D:" + pdfDate;
+ goto TryAgain;
+ }
}
}
- // ReSharper disable once EmptyGeneralCatchClause
- catch (Exception ex)
+ catch (Exception)
{
// If we cannot parse datetime, just eat it, but give a hint in DEBUG build.
- Debug.Assert(false, ex.Message);
+ //Debug.Assert(false, ex.Message);
+ PdfSharpLogHost.PdfReadingLogger.LogError("Date string '{Date}' could not be parsed.", pdfDate);
+ success = false;
}
- return datetime;
+ return success;
}
///
@@ -1850,7 +1962,7 @@ struct ParserState
readonly Dictionary _objectStreamsWithParsers = new();
#endif
readonly Parser _documentParser;
- private int _endStreamNotFoundCounter = 0;
+ int _endStreamNotFoundCounter = 0;
readonly ILogger _logger;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs
index f9b9f6fc..e872c4c4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs
@@ -1,10 +1,13 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using System.ComponentModel;
using Microsoft.Extensions.Logging;
using PdfSharp.Internal;
using PdfSharp.Logging;
+using PdfSharp.Pdf.Forms;
using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
using PdfSharp.Pdf.Internal;
namespace PdfSharp.Pdf.IO
@@ -114,7 +117,6 @@ public static int TestPdfFile(Stream stream)
{
}
}
-
return 0;
}
@@ -208,6 +210,12 @@ public static PdfDocument Open(string path, string password, PdfReaderOptions? o
public static PdfDocument Open(Stream stream)
=> Open(stream, PdfDocumentOpenMode.Modify);
+ ///
+ /// Opens an existing PDF document.
+ ///
+ public static PdfDocument Open(Stream stream, string password, PdfReaderOptions? options = null)
+ => Open(stream, password, PdfDocumentOpenMode.Modify, null, options);
+
///
/// Opens an existing PDF document.
///
@@ -258,7 +266,7 @@ PdfDocument OpenFromFile(string path, string? password, PdfDocumentOpenMode open
}
catch (Exception ex)
{
- PdfSharpLogHost.Logger.LogError(ex, "Open a PDF document failed.");
+ PdfSharpLogHost.Logger.LogError(ex, "Opening a PDF document failed.");
throw;
}
return document!;
@@ -268,7 +276,7 @@ PdfDocument OpenFromFile(string path, string? password, PdfDocumentOpenMode open
/// Opens a PDF document from a stream.
///
PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode openMode,
- PdfPasswordProvider? passwordProvider, PdfReaderOptions? options = null)
+ PdfPasswordProvider? passwordProvider) // MaOs4StLa Review: Removed options parameter. The parameter was not used and there is already an _options field.
{
try
{
@@ -279,8 +287,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
var lexer = new Lexer(stream, _logger);
_document = new PdfDocument(lexer);
- _document._state |= DocumentState.Imported;
- _document._openMode = openMode;
+ _document.State |= DocumentState.Imported;
+ _document.OpenMode = openMode;
try
{
@@ -301,8 +309,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
byte[] header = new byte[1024];
stream.Position = 0;
var _ = stream.Read(header, 0, 1024);
- _document._version = GetPdfFileVersion(header);
- if (_document._version == 0)
+ _document.SetVersion(GetPdfFileVersion(header));
+ if (_document.Version == 0)
throw new InvalidOperationException(PsMsgs.InvalidPdf);
// Set IsUnderConstruction for IrefTable to true. This allows Parser.ParseObject() to insert placeholder references for objects not yet known.
@@ -310,19 +318,27 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
// After reading all objects, all documents placeholder references get replaced by references knowing their objects in FinishReferences(),
// which finally sets IsUnderConstruction to false.
_document.IrefTable.IsUnderConstruction = true;
- var parser = new Parser(_document, options ?? new PdfReaderOptions(), _logger);
+ var parser = new Parser(_document, _options, _logger);
// 1. Read all trailers or cross-reference streams, but no objects.
_document.Trailer = parser.ReadTrailer();
if (_document.Trailer == null!)
ParserDiagnostics.ThrowParserException(
"Invalid PDF file: no trailer found."); // TODO_OLD L10N using PsMsgs
- // References available by now: All references to file-level objects.
- // Reference.Values available by now: All trailers and cross-reference streams (which are not encrypted by definition).
+ // References available by now: All references to file-level objects.
+ // Reference.Values available by now: All trailers and cross-reference streams (which are not encrypted by definition).
// 2. Read the encryption dictionary, if existing.
- if (_document.Trailer!.Elements[PdfTrailer.Keys.Encrypt] is PdfReference xrefEncrypt)
+ // #US373: Should we expect references here?
+ if (_document.Trailer!.Elements[PdfTrailer.Keys.Encrypt] is PdfReference xrefEncrypt) // #US373 Expect a reference here.
{
+#if DEBUG_
+ if (xrefEncrypt.ObjectNumber == 96049)
+ {
+ bool contains = _document.IrefTable.Contains(xrefEncrypt.ObjectID);
+ var xrefExisting = _document.IrefTable[xrefEncrypt.ObjectID];
+ }
+#endif
var encrypt = parser.ReadIndirectObject(xrefEncrypt, null, true);
encrypt.Reference = xrefEncrypt;
xrefEncrypt.Value = encrypt;
@@ -336,6 +352,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
var effectiveSecurityHandler = _document.EffectiveSecurityHandler;
if (effectiveSecurityHandler != null)
{
+ effectiveSecurityHandler.DoNotResetEncryption = _options.DoNotResetEncryption;
+
TryAgain: // ... after the password provider provides a valid password.
// ReSharper disable RedundantIfElseBlock to keep code more readable.
PasswordValidity validity = effectiveSecurityHandler.ValidatePassword(password);
@@ -370,7 +388,15 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
goto TryAgain;
}
else
- throw new PdfReaderException(PsMsgs.OwnerPasswordRequired);
+ {
+#if PDFSHARP_DEBUG
+ // Needed for testing and debugging of encrypted files.
+ if (PdfSharpDebug.Instance.AllowOpenWithUserPasswordOnly)
+ goto ContinueWithoutOwnerPassword;
+#endif
+ if (!_options.AllowModifyWithoutOwnerPassword)
+ throw new PdfReaderException(PsMsgs.OwnerPasswordRequired);
+ }
}
// ReSharper restore RedundantIfElseBlock
}
@@ -383,6 +409,10 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
}
}
+#if PDFSHARP_DEBUG
+ ContinueWithoutOwnerPassword:
+#endif
+
// 4. Read all Objects streams and the references to the objects saved in them.
parser.ReadAllObjectStreamsAndTheirReferences();
// References available by now: All references (to file-level objects and to objects residing in object streams).
@@ -394,7 +424,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
// Reference.Values available by now: All objects.
// 6. Reset encryption so that it must be redefined to save the document encrypted.
- effectiveSecurityHandler?.SetEncryptionToNoneAndResetPasswords();
+ if (!_options.DoNotResetEncryption)
+ effectiveSecurityHandler?.SetEncryptionToNoneAndResetPasswords();
// 7. Replace all document’s placeholder references by references knowing their objects.
// Placeholder references are used, when reading indirect objects referring objects stored in object streams before reading and decoding them.
@@ -422,7 +453,7 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
}
// Change modification date.
- _document.Info.ModificationDate = DateTime.Now;
+ _document.Info.ModificationDate = DateTimeOffset.Now;
// Remove all unreachable objects.
int removed = _document.IrefTable.Compact();
@@ -433,13 +464,29 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
}
// Force flattening of page tree.
- var pages = _document.Pages;
- Debug.Assert(pages != null);
+ _document.Pages.FlattenPageTree();
_document.IrefTable.CheckConsistence();
_document.IrefTable.Renumber();
_document.IrefTable.CheckConsistence();
}
+ else if (openMode == PdfDocumentOpenMode.Import)
+ {
+ // Keep the page tree and generate a flat array of all pages for simple access.
+ _document.Pages.PreservePageTree();
+ }
+ else
+ {
+ throw new InvalidOperationException($"Open mode {openMode.ToString()} does not exist; use Import instead.");
+ }
+
+ // Create metadata.
+ PdfMetadata.MetadataPreparer.PrepareDocument(_document);
+ // Create Acro fields and widgets.
+ PdfFormFields.AcroFieldPreparer.PrepareDocument(_document);
+ // Create annotations.
+ PdfAnnotations.AnnotationPreparer.PrepareDocument(_document);
+
}
catch (Exception ex)
{
@@ -456,6 +503,8 @@ void FinalizeReferences()
{
Debug.Assert(_document.IrefTable.IsUnderConstruction);
+ // TODO: Can a direct container in a top-level object also contain references to be updated?
+
foreach (var iref in _document.IrefTable.AllReferences)
{
var pdfObject = iref.Value;
@@ -463,6 +512,9 @@ void FinalizeReferences()
Debug.Assert(pdfObject != null,
"All references saved in IrefTable should have been created when their referred PdfObject has been accessible.");
+ if (pdfObject.IsDead)
+ throw new InvalidOperationException("TODO: REPORT A BUG"); // TODO
+
// Update all references to PdfDictionary’s and PdfArray’s child objects.
switch (pdfObject)
{
@@ -472,13 +524,14 @@ void FinalizeReferences()
// Instead, enumerate Keys and get value via Elements[key], which should be O(1).
foreach (var key in dictionary.Elements.Keys)
{
- var item = dictionary.Elements[key];
+ var item = dictionary.Elements[key]; // #US373 ??? Do not use GetValue - see below.
// Replace each reference with its final item, if necessary.
if (item is PdfReference currentReference && ShouldUpdateReference(currentReference, out var finalItem))
dictionary.Elements[key] = finalItem;
}
break;
+
case PdfArray array:
var elements = array.Elements;
for (var i = 0; i < elements.Count; i++)
@@ -516,7 +569,7 @@ bool ShouldUpdateReference(PdfReference currentReference, out PdfItem finalItem)
{
// Read the reference for the ObjectID of the placeholder reference from IrefTable, which should contain the value.
var newIref = _document.IrefTable[currentReference.ObjectID];
- reference = newIref;
+ reference = newIref; // New value can be null if no object with the specified ID exists.
isChanged = true;
// reference may be null. Don’t return yet.
}
@@ -574,7 +627,7 @@ static void RereadUnicodeStrings(PdfItem pdfItem)
}
}
- PdfReaderOptions _options;
+ readonly PdfReaderOptions _options;
PdfDocument _document = default!;
readonly ILogger _logger;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs
index ed530d51..a15c5014 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member because it is for internal use only.
@@ -99,8 +99,16 @@ public class PdfReaderOptions
public ReaderProblemDelegate? ReaderProblemCallback { get; set; }
- // Testing only
+ ///
+ /// Allow opening owner password secured PDF files for modification without specifying the owner password.
+ /// The default behavior is to throw an exception and deny the modification to respect the author’s intention.
+ ///
+ public bool AllowModifyWithoutOwnerPassword { get; set; }
- //public bool UseOldCode { get; set; } = false;
+ ///
+ /// Do not reset the encryption of an opened document and reuse these settings when saving.
+ /// The default behaviour is to reset the encryption, so that it’s on the user to set an encryption before saving.
+ ///
+ public bool DoNotResetEncryption { get; set; }
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs
index dbbc1c19..18346cd5 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs
@@ -2,148 +2,143 @@
// See the LICENSE file in the solution root for more information.
using System.Text;
+using PdfSharp.Internal;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.Security;
using PdfSharp.Pdf.Internal;
-using PdfSharp.Pdf.Signatures;
+
+// v7.0.0 Review
namespace PdfSharp.Pdf.IO
{
///
/// Represents a writer for generation of PDF streams.
///
- class PdfWriter
+ class PdfWriter(Stream pdfStream, PdfDocument document, PdfStandardSecurityHandler? effectiveSecurityHandler)
{
- public PdfWriter(Stream pdfStream, PdfDocument document, PdfStandardSecurityHandler? effectiveSecurityHandler)
- {
- _stream = pdfStream ?? throw new ArgumentNullException(nameof(pdfStream));
- _document = document ?? throw new ArgumentNullException(nameof(document));
- EffectiveSecurityHandler = effectiveSecurityHandler;
-#if DEBUG
- Layout = PdfWriterLayout.Verbose;
-#endif
- }
-
- public void Close(bool closeUnderlyingStream)
+ public void Close(bool closeUnderlyingStream = true)
{
if (closeUnderlyingStream)
_stream.Close();
_stream = null!;
}
- public void Close() => Close(true);
-
+ // ReSharper disable once RedundantCast because SizeType can be a 32- or 64-bit integer.
public SizeType Position => (SizeType)_stream.Position;
///
/// Gets or sets the kind of layout.
///
- public PdfWriterLayout Layout { get; set; }
+ public PdfWriterLayout Layout { get; set; } = document.Options.Layout;
+
+ internal bool IsCompactLayout => Layout == PdfWriterLayout.Compact;
+
+ internal bool IsStandardLayout => Layout >= PdfWriterLayout.Standard;
+
+ internal bool IsIndentedLayout => Layout >= PdfWriterLayout.Indented;
+
+ internal bool IsVerboseLayout => Layout >= PdfWriterLayout.Verbose;
public PdfWriterOptions Options { get; set; }
// -----------------------------------------------------------
///
- /// Writes the specified value to the PDF stream.
+ /// Writes a PDF comment to the PDF stream.
///
- public void Write(bool value)
+ public void WriteComment(string text)
{
WriteSeparator(CharCat.Character);
- // Wrong: Writes "True" or "False" where it should be "true" or "false": WriteRaw(value ? bool.TrueString : bool.FalseString);
- WriteRaw(value ? "true" : "false");
- _lastCat = CharCat.Character;
+ WriteRaw("% " + text + '\n');
+ }
+ ///
+ /// Writes ‘mull’ to the PDF stream.
+ ///
+ public void Write(PdfNull _)
+ {
+ WriteSeparator(CharCat.Character);
+ WriteRaw("null");
}
///
- /// Writes the specified value to the PDF stream.
+ /// Writes ‘true’ or ‘false’ to the PDF stream.
///
- public void Write(PdfBoolean value)
+ public void Write(bool value)
{
WriteSeparator(CharCat.Character);
- WriteRaw(value.Value ? "true" : "false");
- _lastCat = CharCat.Character;
+ WriteRaw(value ? "true" : "false");
}
///
- /// Writes the specified value to the PDF stream.
+ /// Writes ‘true’ or ‘false’ to the PDF stream.
///
- public void Write(int value)
+ public void Write(PdfBoolean value) => Write(value.Value);
+
+ ///
+ /// Writes the specified integer value to the PDF stream.
+ ///
+ public void Write(int value, bool isFlag)
{
WriteSeparator(CharCat.Character);
- WriteRaw(value.ToString(CultureInfo.InvariantCulture));
- _lastCat = CharCat.Character;
+ if (isFlag)
+ {
+ // Maybe an unsigned value instead of a negative one causes
+ // problems with some PDF readers.
+ //WriteRaw(((uint)value).ToString(CultureInfo.InvariantCulture));
+ WriteRaw(value.ToString(CultureInfo.InvariantCulture));
+ if (IsVerboseLayout)
+ {
+ WriteRaw($" % 0x{(uint)value >> 4:X4}_{(uint)value & 0xFFFF:X4}\n");
+ }
+ }
+ else
+ WriteRaw(value.ToString(CultureInfo.InvariantCulture));
}
+ ///
+ /// Writes the specified long integer value to the PDF stream.
+ ///
public void Write(long value)
{
WriteSeparator(CharCat.Character);
WriteRaw(value.ToString(CultureInfo.InvariantCulture));
- _lastCat = CharCat.Character;
}
///
- /// Writes the specified value to the PDF stream.
+ /// Writes the specified unsigned integer value to the PDF stream.
///
public void Write(uint value)
{
WriteSeparator(CharCat.Character);
WriteRaw(value.ToString(CultureInfo.InvariantCulture));
- _lastCat = CharCat.Character;
}
///
- /// Writes the specified value to the PDF stream.
+ /// Writes the specified integer value to the PDF stream.
///
- public void Write(PdfInteger value)
- {
- WriteSeparator(CharCat.Character);
- _lastCat = CharCat.Character;
- WriteRaw(value.Value.ToString(CultureInfo.InvariantCulture));
- }
-
- // DELETE
- //// ///
- //// /// Writes the specified value to the PDF stream.
- //// ///
- ////#pragma warning disable CS0618 // Type or member is obsolete
- //// public void Write(PdfUInteger value)
- ////#pragma warning restore CS0618 // Type or member is obsolete
- //// {
- //// WriteSeparator(CharCat.Character);
- //// _lastCat = CharCat.Character;
- //// WriteRaw(value.Value.ToString(CultureInfo.InvariantCulture));
- //// }
+ public void Write(PdfInteger value) => Write(value.Value, value.IsFlag);
///
- /// Writes the specified value to the PDF stream.
+ /// Writes the specified long integer value to the PDF stream.
///
- public void Write(PdfLongInteger value)
- {
- WriteSeparator(CharCat.Character);
- _lastCat = CharCat.Character;
- WriteRaw(value.Value.ToString(CultureInfo.InvariantCulture));
- }
+ public void Write(PdfLongInteger value) => Write(value.Value);
///
- /// Writes the specified value to the PDF stream.
+ /// Writes the specified real value to the PDF stream.
///
public void Write(double value)
{
+ // See unit test Test_Single_Write_and_Read to understand why double values now
+ // are written as singles values.
+ float f = (float)value;
WriteSeparator(CharCat.Character);
- WriteRaw(value.ToString(Config.SignificantDecimalPlaces7, CultureInfo.InvariantCulture));
- _lastCat = CharCat.Character;
+ WriteRaw(f.ToString(Config.SignificantDecimalPlaces7, CultureInfo.InvariantCulture));
}
///
- /// Writes the specified value to the PDF stream.
+ /// Writes the specified real value to the PDF stream.
///
- public void Write(PdfReal value)
- {
- WriteSeparator(CharCat.Character);
- WriteRaw(value.Value.ToString(Config.SignificantDecimalPlaces7, CultureInfo.InvariantCulture));
- _lastCat = CharCat.Character;
- }
+ public void Write(PdfReal value) => Write(value.Value);
///
/// Writes the specified value to the PDF stream.
@@ -151,62 +146,50 @@ public void Write(PdfReal value)
public void Write(PdfString value)
{
WriteSeparator(CharCat.Delimiter);
-#if true
- PdfStringEncoding encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask);
+ var encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask);
string pdf = (value.Flags & PdfStringFlags.HexLiteral) == 0 ?
PdfEncoders.ToStringLiteral(value.Value, encoding, EffectiveSecurityHandler) :
PdfEncoders.ToHexStringLiteral(value.Value, encoding, EffectiveSecurityHandler);
WriteRaw(pdf);
-#else
- switch (value.Flags & PdfStringFlags.EncodingMask)
- {
- case PdfStringFlags.Undefined:
- case PdfStringFlags.PDFDocEncoding:
- if ((value.Flags & PdfStringFlags.HexLiteral) == 0)
- WriteRaw(PdfEncoders.DocEncode(value.Value, false));
- else
- WriteRaw(PdfEncoders.DocEncodeHex(value.Value, false));
- break;
-
- case PdfStringFlags.WinAnsiEncoding:
- throw new NotImplementedException("Unexpected encoding: WinAnsiEncoding");
-
- case PdfStringFlags.Unicode:
- if ((value.Flags & PdfStringFlags.HexLiteral) == 0)
- WriteRaw(PdfEncoders.DocEncode(value.Value, true));
- else
- WriteRaw(PdfEncoders.DocEncodeHex(value.Value, true));
- break;
-
- case PdfStringFlags.StandardEncoding:
- case PdfStringFlags.MacRomanEncoding:
- case PdfStringFlags.MacExpertEncoding:
- default:
- throw new NotImplementedException("Unexpected encoding");
- }
-#endif
- _lastCat = CharCat.Delimiter;
+ //_lastCat = CharCat.Delimiter;
}
///
/// Writes a signature placeholder hexadecimal string to the PDF stream.
///
- public void Write(PdfSignaturePlaceholderItem item, out SizeType startPosition, out SizeType endPosition)
+ public void Write(PdfPlaceholder item, out SizeType startPosition, out SizeType endPosition)
{
WriteSeparator(CharCat.Delimiter);
+ // ReSharper disable once RedundantCast
+ startPosition = (SizeType)Position;
+
+ // We have to write effectively filler bytes to easily update the stream postion.
+ WriteRaw(item.Value);
+
+ // ReSharper disable once RedundantCast
+ endPosition = (SizeType)Position;
+ }
+
+#if false
+ /// // DELETE
+ /// Writes a signature placeholder hexadecimal string to the PDF stream.
+ ///
+ public void Write(PdfSignaturePlaceholderItem_ item, out SizeType startPosition, out SizeType endPosition) // TODO: Use PdfPlaceholder and DELETE
+ {
+ WriteSeparator(CharCat.Delimiter);
// ReSharper disable once RedundantCast
startPosition = (SizeType)Position;
// A PDF hex string with question marks '???????...??>'
WriteRaw(item.ToString());
// ReSharper disable once RedundantCast
endPosition = (SizeType)Position;
-
- _lastCat = CharCat.Delimiter;
+ //_lastCat = CharCat.Delimiter;
}
+#endif
///
- /// Writes the specified value to the PDF stream.
+ /// Writes the specified PDF name to the PDF stream.
///
public void Write(PdfName value)
{
@@ -222,161 +205,178 @@ public void Write(PdfName value)
// In such situations, it is recommended that the sequence of bytes (after expansion
// of # sequences, if any) be interpreted according to UTF-8, a variable-length byte-encoded
// representation of Unicode in which the printable ASCII characters have
- // the same representations as in ASCII.This enables a name object to represent text
+ // the same representations as in ASCII. This enables a name object to represent text
// in any natural language, subject to the implementation limit on the length of a
// name.
- WriteSeparator(CharCat.Delimiter/*, '/'*/);
- string name = value.Value;
- Debug.Assert(name[0] == '/');
-
- // Encode to raw UTF-8 is any char is larger than 126.
- // 127 [DEL] is not a valid value and is also get encoded.
- for (int idx = 1; idx < name.Length; idx++)
- {
- char ch = name[idx];
- if (ch > 126)
- {
- // Non-ASCII character found, convert whole string to raw UTF-8.
- var bytes = Encoding.UTF8.GetBytes(name);
- var nameBuilder = new StringBuilder();
- foreach (var ch2 in bytes)
- nameBuilder.Append((char)ch2);
- name = nameBuilder.ToString();
- break;
- }
- }
-
- // Here all high bytes are zero.
- var pdf = new StringBuilder("/");
- for (int idx = 1; idx < name.Length; idx++)
- {
- char ch = name[idx];
- Debug.Assert(ch < 256);
- if (ch > ' ')
- {
- switch (ch)
- {
- case '%':
- case '/':
- case '<':
- case '>':
- case '(':
- case ')':
- case '[':
- case ']':
- case '#':
- break;
-
- default:
- if (ch <= 126)
- {
- // See recommendation above.
- pdf.Append(ch);
- continue;
- }
- break;
- }
- }
- pdf.AppendFormat("#{0:X2}", (int)ch);
- }
- WriteRaw(pdf.ToString());
+ WriteSeparator(CharCat.Delimiter);
+ var literalName = value.Name.LiteralValue;
+ WriteRaw(literalName);
+ // In the rare case someone used an empty name the last category must
+ // nevertheless set to Character. Otherwise, a subsequent number would be parsed as
+ // part of the name if the writer mode is Compact.
+ _lastChar = '0'; // 0 as a symbolic character.
_lastCat = CharCat.Character;
}
+ ///
+ /// Writes the specified PDF literal value to the PDF stream.
+ ///
public void Write(PdfLiteral value)
{
- WriteSeparator(CharCat.Character);
+ if (String.IsNullOrEmpty(value.Value))
+ return;
+
+ WriteSeparator(GetCategory(value.Value[0]));
WriteRaw(value.Value);
- _lastCat = CharCat.Character;
}
+ ///
+ /// Writes the specified PDF rectangle value to the PDF stream.
+ ///
public void Write(PdfRectangle rect)
{
const string format = Config.SignificantDecimalPlaces3;
- WriteSeparator(CharCat.Delimiter/*, '/'*/);
+ WriteSeparator(CharCat.Delimiter);
WriteRaw(PdfEncoders.Format("[{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "}]", rect.X1, rect.Y1, rect.X2, rect.Y2));
- _lastCat = CharCat.Delimiter;
}
+ ///
+ /// Writes the specified PDF reference value to the PDF stream.
+ ///
public void Write(PdfReference iref)
{
+#if DEBUG_
+ if (iref.ObjectID.ObjectNumber == 6)
+ _ = typeof(int);
+#endif
WriteSeparator(CharCat.Character);
WriteRaw(iref.ToString());
- _lastCat = CharCat.Character;
}
public void WriteDocString(string text, bool unicode)
{
WriteSeparator(CharCat.Delimiter);
- //WriteRaw(PdfEncoders.DocEncode(text, unicode));
- byte[] bytes;
- if (!unicode)
- bytes = PdfEncoders.DocEncoding.GetBytes(text);
- else
- bytes = PdfEncoders.UnicodeEncoding.GetBytes(text);
+ var bytes = unicode
+ ? PdfEncoders.UnicodeEncoding.GetBytes(text)
+ : PdfEncoders.DocEncoding.GetBytes(text);
bytes = PdfEncoders.FormatStringLiteral(bytes, unicode, true, false, EffectiveSecurityHandler);
Write(bytes);
- _lastCat = CharCat.Delimiter;
}
+ ///
+ /// Writes the specified PDF DOC encoded string value to the PDF stream.
+ ///
public void WriteDocString(string text)
{
WriteSeparator(CharCat.Delimiter);
- //WriteRaw(PdfEncoders.DocEncode(text, false));
byte[] bytes = PdfEncoders.DocEncoding.GetBytes(text);
bytes = PdfEncoders.FormatStringLiteral(bytes, false, false, false, EffectiveSecurityHandler);
Write(bytes);
- _lastCat = CharCat.Delimiter;
}
+ ///
+ /// Writes the specified PDF DOC encoded string value to the PDF stream.
+ ///
public void WriteDocStringHex(string text)
{
WriteSeparator(CharCat.Delimiter);
- //WriteRaw(PdfEncoders.DocEncodeHex(text));
byte[] bytes = PdfEncoders.DocEncoding.GetBytes(text);
bytes = PdfEncoders.FormatStringLiteral(bytes, false, false, true, EffectiveSecurityHandler);
- _stream.Write(bytes, 0, bytes.Length);
- _lastCat = CharCat.Delimiter;
+ Write(bytes);
}
///
/// Begins a direct or indirect dictionary or array.
///
- public void WriteBeginObject(PdfObject obj)
+ public void WriteBeginObject(PdfObject obj, int childItemCount = 0)
{
- bool indirect = obj.IsIndirect;
- if (indirect)
- {
+#if DEBUG
+ if (obj.ObjectID.ObjectNumber == 10)
+ _ = typeof(int);
+#endif
+ bool isIndirect = obj.IsIndirect;
+ if (isIndirect)
WriteObjectAddress(obj);
- EffectiveSecurityHandler?.EnterObject(obj.ObjectID);
- }
- _stack.Add(new StackItem(obj));
- if (indirect)
+
+ _stack.Add(new StackItem(obj, childItemCount));
+
+ string? suffix = null;
+ if (IsVerboseLayout && _stack.Count > 1)
+ suffix = GetTypeAndComment(obj);
+
+ if (isIndirect)
{
if (obj is PdfArray)
- WriteRaw("[\n");
+ {
+ if (IsCompactLayout)
+ {
+ WriteRaw('[');
+ }
+ else
+ {
+ if (suffix != null)
+ WriteRaw("[ " + suffix);
+ else
+ WriteRaw("[");
+ }
+ }
else if (obj is PdfDictionary)
- WriteRaw("<<\n");
- _lastCat = CharCat.NewLine;
+ {
+ if (IsCompactLayout)
+ WriteRaw("<<");
+ else
+ {
+ if (suffix != null)
+ WriteRaw("<< " + suffix);
+ else
+ WriteRaw("<<\n");
+ }
+ }
+ else
+ {
+ // Case: PdfIntegerObject or PdfNullObject
+
+ Debug.Assert(obj is not null, "Should not come here.");
+ Debug.Assert(obj is PdfPrimitiveObject, "Should be primitive object.");
+ }
}
- else
+ else // !isIndirect
{
if (obj is PdfArray)
{
- WriteSeparator(CharCat.Delimiter);
- WriteRaw('[');
- _lastCat = CharCat.Delimiter;
+ if (IsCompactLayout)
+ WriteRaw('[');
+ else
+ {
+ if (suffix != null)
+ WriteRaw(" [ " + suffix);
+ else
+ WriteRaw(" [");
+ }
}
else if (obj is PdfDictionary)
{
- NewLine();
- WriteSeparator(CharCat.Delimiter);
- WriteRaw("<<\n");
- _lastCat = CharCat.NewLine;
+ if (IsCompactLayout)
+ WriteRaw("<<");
+ else
+ {
+ NewLine();
+ WriteSeparator(CharCat.Delimiter);
+ if (suffix != null)
+ WriteRaw("<< " + suffix);
+ else
+ WriteRaw("<<\n");
+ }
+ }
+ else
+ {
+ // Case: PdfIntegerObject or PdfNullObject
+
+ Debug.Assert(obj is not null, "Should not come here.");
}
}
- if (Layout == PdfWriterLayout.Verbose)
+ if (IsIndentedLayout)
IncreaseIndent();
}
@@ -388,77 +388,129 @@ public void WriteEndObject()
int count = _stack.Count;
Debug.Assert(count > 0, "PdfWriter stack underflow.");
- StackItem stackItem = _stack[count - 1];
+ var stackItem = _stack[count - 1];
_stack.RemoveAt(count - 1);
PdfObject value = stackItem.Object;
var indirect = value.IsIndirect;
- if (indirect)
- EffectiveSecurityHandler?.LeaveObject();
- if (Layout == PdfWriterLayout.Verbose)
+
+ if (IsIndentedLayout)
DecreaseIndent();
+
if (value is PdfArray)
{
if (indirect)
{
- WriteRaw("\n]\n");
- _lastCat = CharCat.NewLine;
+ if (IsCompactLayout)
+ WriteRaw("]\n");
+ else
+ WriteRaw("]");
}
else
{
- WriteRaw("]");
- _lastCat = CharCat.Delimiter;
+ // DELETE TODO
+ if (IsCompactLayout)
+ {
+ WriteRaw("]");
+ }
+ else
+ {
+ // Indent after new line.
+ if (_lastCat == CharCat.NewLine && IsIndentedLayout)
+ WriteIndent();
+ WriteRaw("]");
+ }
}
}
else if (value is PdfDictionary)
{
if (indirect)
{
- if (!stackItem.HasStream)
- WriteRaw(_lastCat == CharCat.NewLine ? ">>\n" : " >>\n");
+ if (IsCompactLayout)
+ {
+ if (!stackItem.HasStream)
+ WriteRaw(">>\n");
+ }
+ else
+ {
+ if (!stackItem.HasStream)
+ WriteRaw(">>\n");
+ }
}
else
{
Debug.Assert(!stackItem.HasStream, "Direct object with stream??");
- WriteSeparator(CharCat.NewLine);
- WriteRaw(">>\n");
- _lastCat = CharCat.NewLine;
+ if (IsCompactLayout)
+ {
+ WriteSeparator(CharCat.NewLine);
+ WriteRaw(">>");
+ }
+ else
+ {
+ WriteSeparator(CharCat.NewLine);
+ //WriteRaw(">>\n");
+ if (IsVerboseLayout)
+ WriteRaw(">>\n");
+ else
+ WriteRaw(">>");
+ }
}
}
if (indirect)
{
- NewLine();
- WriteRaw("endobj\n");
- if (Layout == PdfWriterLayout.Verbose)
- WriteRaw("%--------------------------------------------------------------------------------------------------\n");
+ if (IsCompactLayout)
+ {
+ NewLine();
+ WriteRaw("endobj\n");
+ }
+ else
+ {
+ NewLine();
+ WriteRaw("endobj\n");
+ if (IsVerboseLayout)
+ WriteRaw("%--------------------------------------------------------------------------------------------------\n");
+ }
}
}
///
/// Writes the stream of the specified dictionary.
///
- public void WriteStream(PdfDictionary value, bool omitStream)
+ public void WriteStream(PdfDictionary dict, bool omitStream)
{
var stackItem = _stack[^1];
Debug.Assert(stackItem.Object is PdfDictionary);
Debug.Assert(stackItem.Object.IsIndirect);
stackItem.HasStream = true;
- WriteRaw(_lastCat == CharCat.NewLine ? ">>\nstream\n" : " >>\nstream\n");
-
- if (omitStream)
+ var bytes = dict.Stream!.Value;
+ if (IsCompactLayout)
{
- WriteRaw(" «…stream content omitted…»\n"); // useful for debugging only
+ WriteRaw(">>stream\n");
+
+ // Earlier versions of PDFsharp skipped the '\n' before 'endstream' if the last byte of
+ // the stream is a linefeed. This was wrong and is now fixed.
+ Write(bytes);
+
+ WriteRaw("\nendstream\n");
}
else
{
- // Earlier versions of PDFsharp skipped the '\n' before 'endstream' if the last byte of
- // the stream is a linefeed. This was wrong and now fixed.
- var bytes = value.Stream.Value;
- if (bytes.Length != 0)
+ if (IsVerboseLayout)
+ WriteRaw(Invariant($">>\n% Real length: {bytes.Length}\nstream\n"));
+ else
+ WriteRaw(">>\nstream\n");
+
+ if (omitStream)
+ {
+ WriteRaw(" «…stream content omitted…»\n"); // Useful for debugging only. PDF file is always invalid.
+ }
+ else
+ {
Write(bytes);
+ }
+ WriteRaw("\nendstream\n");
}
- WriteRaw("\nendstream\n");
}
public void WriteRaw(string rawString)
@@ -468,7 +520,8 @@ public void WriteRaw(string rawString)
byte[] bytes = PdfEncoders.RawEncoding.GetBytes(rawString);
_stream.Write(bytes, 0, bytes.Length);
- _lastCat = GetCategory((char)bytes[^1]);
+ _lastChar = (char)bytes[^1];
+ _lastCat = GetCategory(_lastChar);
}
public void WriteRaw(char ch)
@@ -476,6 +529,7 @@ public void WriteRaw(char ch)
Debug.Assert(ch < 256, "Raw character greater than 255 detected.");
_stream.WriteByte((byte)ch);
+ _lastChar = ch;
_lastCat = GetCategory(ch);
}
@@ -485,60 +539,62 @@ public void Write(byte[] bytes)
return;
_stream.Write(bytes, 0, bytes.Length);
- _lastCat = GetCategory((char)bytes[^1]);
+ _lastChar = (char)bytes[^1];
+ _lastCat = GetCategory(_lastChar);
}
void WriteObjectAddress(PdfObject value)
{
- if (Layout == PdfWriterLayout.Verbose)
+ var objID = value.ObjectID;
+ if (IsVerboseLayout)
{
- string comment = value.Comment;
- if (!String.IsNullOrEmpty(comment))
- comment = $" -- {value.Comment}";
-
-#if DEBUG_
- if (_document is null)
- _ = typeof(int);
-#endif
// #PDF-A
if (_document.IsPdfA)
{
// Write full type name and comment in a separate line to be PDF-A conform.
- WriteRaw(Invariant($"% {value.GetType().FullName}{comment}\n"));
- WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj\n"));
+ WriteRaw(GetTypeAndComment(value)!);
+ WriteRaw(Invariant($"{objID.ObjectNumber} {objID.GenerationNumber} obj\n"));
}
else
{
// Write object number and full type name and comment in one line.
- WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj % {value.GetType().FullName}{comment}\n"));
+ WriteRaw(Invariant($"{objID.ObjectNumber} {objID.GenerationNumber} obj {GetTypeAndComment(value, true)}"));
}
}
else
{
// Write object number only.
- WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj\n"));
+ WriteRaw(Invariant($"{objID.ObjectNumber} {objID.GenerationNumber} obj\n"));
}
}
public void WriteFileHeader(PdfDocument document)
{
var header = new StringBuilder("%PDF-");
- int version = document._version;
+ int version = document.Version;
//header.Append((version / 10).ToString(CultureInfo.InvariantCulture) + "." +
// (version % 10).ToString(CultureInfo.InvariantCulture) + "\n%\xD3\xF4\xCC\xE1\n");
header.Append(Invariant($"{version / 10}.{version % 10}\n%\xD3\xF4\xCC\xE1\n"));
WriteRaw(header.ToString());
- if (Layout == PdfWriterLayout.Verbose)
+ if (IsVerboseLayout)
{
- WriteRaw($"% PDFsharp Version {PdfSharpProductVersionInformation.Version} (verbose mode)\n");
+ WriteRaw($"% PDFsharp {PdfSharpProductVersionInformation.Version} (verbose layout)\n");
+ WriteRaw($"% Version {PdfSharpProductVersionInformation.SemanticVersion} ({Capabilities.Build.BuildName}) under .NET {Capabilities.Build.Framework}\n");
// Keep some space for later fix-up.
_commentPosition = (int)_stream.Position + 2;
- WriteRaw("% \n"); // Creation date placeholder
- WriteRaw("% \n"); // Creation time placeholder
- WriteRaw("% \n"); // File size placeholder
- WriteRaw("% \n"); // Pages placeholder
- WriteRaw("% \n"); // Objects placeholder
+ WriteRaw("% \n"); // Title placeholder
+ WriteRaw("% \n"); // Creation date placeholder
+ WriteRaw("% \n"); // Creation time placeholder
+ WriteRaw("% \n"); // File size placeholder
+ WriteRaw("% \n"); // Pages placeholder
+ WriteRaw("% \n"); // Objects placeholder
+ WriteRaw("% \n"); // PDF/A placeholder
+ WriteRaw("% \n"); // Embedded files placeholder
+ WriteRaw("% \n"); // Signature type placeholder
+ WriteRaw("% \n"); // Encryption type placeholder
+ WriteRaw("% \n"); // User password placeholder
+ WriteRaw("% \n"); // Owner password placeholder
#if DEBUG
WriteRaw("% This document is created from a DEBUG build. Do not use a DEBUG build of PDFsharp for production.\n");
#endif
@@ -546,33 +602,123 @@ public void WriteFileHeader(PdfDocument document)
}
}
+ // ReSharper disable once IdentifierTypo
public void WriteEof(PdfDocument document, SizeType startxref)
{
WriteRaw($"% Created with PDFsharp {PdfSharpProductVersionInformation.SemanticVersion} ({Capabilities.Build.BuildName}) under .NET {Capabilities.Build.Framework}\n");
+#if DEBUG
+ WriteRaw($"% Branch {SemVersionInformation.BranchName} ({SemVersionInformation.Sha}) {SemVersionInformation.CommitDate}\n");
+#endif
WriteRaw("startxref\n");
WriteRaw(startxref.ToString(CultureInfo.InvariantCulture));
WriteRaw("\n%%EOF\n");
SizeType fileSize = (SizeType)_stream.Position;
- if (Layout == PdfWriterLayout.Verbose)
+ if (IsVerboseLayout)
{
- TimeSpan duration = DateTime.Now - document._creation;
+ // Note
+ // Without InvariantCulture parameter the following code fails if the current culture is e.g.
+ // a Far East culture, because the date string may contain non-ASCII characters.
+ // So never never never never use ToString without a culture info.
+
+ var now = DateTimeOffset.Now;
+ TimeSpan duration = now - document.CreationDate;
+ const int offset = 60;
+
+ // Set stream to document comment section.
_stream.Position = _commentPosition;
- // Without InvariantCulture parameter the following line fails if the current culture is e.g.
- // a Far East culture, because the date string contains non-ASCII characters.
- // So never never never never use ToString without a culture info.
- WriteRaw(Invariant($"Creation date: {document._creation:G}"));
- _stream.Position = _commentPosition + 50;
- WriteRaw(Invariant($"Creation time: {duration.TotalSeconds:0.000} seconds"));
- _stream.Position = _commentPosition + 100;
- WriteRaw(Invariant($"File size: {fileSize:#,###} bytes"));
- _stream.Position = _commentPosition + 150;
- WriteRaw(Invariant($"Pages: {document.Pages.Count:#}")); // No thousands separator here.
- _stream.Position = _commentPosition + 200;
- WriteRaw(Invariant($"Objects: {document.IrefTable.Count:#,###}"));
+
+ var title = _document.Info.Title;
+ title = (title.Length switch
+ {
+ > 46 => "'" + title[..43] + "...'",
+ > 0 => "'" + title + "'",
+ _ => "«not set»" + new String(' ', 39)
+ } + " ")[..48];
+ WriteRaw(Invariant($"Title {title}"));
+
+ // Always write the real creation date here, i.e. the time on the producing operating system.
+ _stream.Position = _commentPosition + offset;
+ WriteRaw(Invariant($"Creation date: ·· {now:yyyy-MM-dd HH:mm:sszzz}"));
+
+ _stream.Position = _commentPosition + 2 * offset;
+ WriteRaw(Invariant($"Creation time: ·· {duration.TotalSeconds:0.0##} seconds"));
+
+ _stream.Position = _commentPosition + 3 * offset;
+ WriteRaw(Invariant($"File size ······· {fileSize:#,###} bytes"));
+
+ _stream.Position = _commentPosition + 4 * offset;
+ WriteRaw(Invariant($"Pages ··········· {document.Pages.Count:#}")); // No thousands separator here.
+
+ _stream.Position = _commentPosition + 5 * offset;
+ WriteRaw(Invariant($"Ind. objects ···· {document.IrefTable.Count:#,###}"));
+
+ _stream.Position = _commentPosition + 6 * offset;
+ WriteRaw(Invariant($"PDF/A ··········· {document.GetPdfAManager().Format.Name}"));
+
+ _stream.Position = _commentPosition + 7 * offset;
+ WriteRaw(Invariant($"Embedded files ·· {document.GetEmbeddedFilesManager().FileCount}"));
+
+ _stream.Position = _commentPosition + 8 * offset;
+ WriteRaw(Invariant($"Signature type ·· {document.GetSigningManager().SignatureType}"));
+
+ var sm = document.GetSecurityManager();
+
+ _stream.Position = _commentPosition + 9 * offset;
+ WriteRaw(Invariant($"Encryption type · {sm.EncryptionType}"));
+
+ _stream.Position = _commentPosition + 10 * offset;
+ WriteRaw(Invariant($"User password ··· {sm.UserPassword}"));
+
+ _stream.Position = _commentPosition + 11 * offset;
+ WriteRaw(Invariant($"Owner password ·· {sm.OwnerPassword}"));
+
+ //“” „“ ’ ‘’ ‚‘ »« ›‹ –
+ //· × ² ³ ½ € † …
+ //✔ ✘ ↯ ± − × ÷ ⋅ √ ≠ ≤ ≥ ≡
+ //® © ← ↑ → ↓ ↔ ↕ ∅
+ //✔⇒
}
}
+ //static string GetFullTypeName(PdfObject obj) => obj.GetType().FullName ?? "?";
+ static string? GetTypeAndComment(PdfObject value, bool typenameAlways = false)
+ {
+ var type = value.GetType();
+ string comment = value.Comment;
+
+ bool showType = typenameAlways || (type != typeof(PdfDictionary) && type != typeof(PdfArray));
+ string? result;
+
+ if (showType)
+ {
+ if (type == typeof(PdfArray) || type == typeof(PdfDictionary))
+ {
+ if (!String.IsNullOrEmpty(comment))
+ result = Invariant($"% {value.GetType().Name} -- {comment}\n");
+ else
+ result = $"% {value.GetType().Name}\n";
+ }
+ else
+ {
+ var name = type.Name;
+ var fullName = type.FullName;
+ if (!String.IsNullOrEmpty(comment))
+ result = Invariant($"% {value.GetType().Name} ({value.GetType().FullName}) -- {comment}\n");
+ else
+ result = $"% {value.GetType().Name} ({value.GetType().FullName})\n";
+ }
+ }
+ else
+ {
+ if (!String.IsNullOrEmpty(comment))
+ result = Invariant($"% {comment}\n");
+ else
+ result = null;
+ }
+ return result;
+ }
+
///
/// Gets or sets the indentation for a new indentation level.
///
@@ -588,52 +734,70 @@ internal int Indent
///
/// Increases indent level.
///
- void IncreaseIndent()
- {
- _writeIndent += _indent;
- }
+ void IncreaseIndent() => _writeIndent += _indent;
///
/// Decreases indent level.
///
- void DecreaseIndent()
- {
- _writeIndent -= _indent;
- }
+ void DecreaseIndent() => _writeIndent -= _indent;
///
/// Gets an indent string of current indent.
///
string IndentBlanks => new(' ', _writeIndent);
- void WriteIndent()
- {
- WriteRaw(IndentBlanks);
- }
+ void WriteIndent() => WriteRaw(IndentBlanks);
- void WriteSeparator(CharCat cat /*, char ch = '\0'*/)
+ ///
+ /// Writes a space depending on the category of the last character.
+ ///
+ ///
+ void WriteSeparator(CharCat cat)
{
- switch (_lastCat)
+ if (IsCompactLayout)
{
- case CharCat.NewLine:
- if (Layout == PdfWriterLayout.Verbose)
- WriteIndent();
- break;
-
- case CharCat.Delimiter:
- break;
+ // Write a blank only if two characters are neighbors.
+ switch (_lastCat)
+ {
+ case CharCat.NewLine:
+ case CharCat.Delimiter:
+ break;
- case CharCat.Character:
- if (Layout == PdfWriterLayout.Verbose)
- {
- _stream.WriteByte((byte)' ');
- }
- else
- {
+ case CharCat.Character:
if (cat == CharCat.Character)
- _stream.WriteByte((byte)' ');
- }
- break;
+ {
+ //_stream.WriteByte((byte)' ');
+ //_lastCat = CharCat.Delimiter;
+ WriteRaw(' ');
+ }
+ break;
+ }
+ }
+ else
+ {
+ switch (_lastCat)
+ {
+ case CharCat.NewLine:
+ if (IsIndentedLayout)
+ WriteIndent();
+ break;
+
+ case CharCat.Delimiter:
+ // Case e.g. “[0 […]0]” should be “[0 […] 0]”. And yes, that’s very nerdy to fix such details.
+ if (_lastChar is ']' or '>' or ')' or '}') // '}' may occur in PostScript functions.
+ WriteRaw(' ');
+ break;
+
+ case CharCat.Character:
+ if (IsStandardLayout)
+ WriteRaw(' ');
+ else
+ {
+ if (cat == CharCat.Character)
+ WriteRaw(' ');
+ }
+ break;
+ }
}
}
@@ -654,25 +818,48 @@ static CharCat GetCategory(char ch)
enum CharCat
{
+ ///
+ /// A LF character.
+ ///
NewLine,
+
+ ///
+ /// A regular character as used in keywords ('true', 'false', 'null', or 'R') or a number used
+ /// in an indirect reference or literals.
+ ///
Character,
+
+ ///
+ /// A PDF delimiter character ('(', ',')', '<', '>', '[', ']', '{', '}', '/')
+ ///
Delimiter,
}
+
+ ///
+ /// Character Category of the last character written.
+ ///
CharCat _lastCat;
+ ///
+ /// Last character of the last character written.
+ /// Needed for optimal verbose layout.
+ ///
+ char _lastChar;
+
///
/// Gets the underlying stream.
///
internal Stream Stream => _stream;
- Stream _stream;
- readonly PdfDocument _document;
+ Stream _stream = pdfStream ?? throw new ArgumentNullException(nameof(pdfStream));
+ readonly PdfDocument _document = document ?? throw new ArgumentNullException(nameof(document));
- internal PdfStandardSecurityHandler? EffectiveSecurityHandler { get; set; }
+ internal PdfStandardSecurityHandler? EffectiveSecurityHandler { get; set; } = effectiveSecurityHandler;
- class StackItem(PdfObject value)
+ class StackItem(PdfObject value, int childItemCount)
{
public readonly PdfObject Object = value;
+ public readonly int ChildItemCount = childItemCount;
public bool HasStream;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfDocumentOpenMode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfDocumentOpenMode.cs
index 03f4cd94..d4149e57 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfDocumentOpenMode.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfDocumentOpenMode.cs
@@ -17,7 +17,7 @@ public enum PdfDocumentOpenMode
///
/// The PDF stream is opened for importing pages from it. A document opened in this mode cannot
- /// be modified.
+ /// be modified, but you can extract pages from it.
///
Import,
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs
index ba0b69c3..77cc6a6c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs
@@ -27,8 +27,8 @@ public enum PdfWriterLayout
///
/// The PDF stream is indented to reflect the nesting levels of the objects and contains additional
- /// information about the PDFsharp objects. Furthermore, content streams are not deflated. This
- /// is useful for debugging purposes only and increases the size of the file significantly.
+ /// information about the type of PDFsharp objects. Furthermore, content streams are not deflated.
+ /// This is useful for debugging purposes only and increases the size of the file significantly.
///
Verbose,
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterOptions.cs
index 312e2fbd..aa765dcd 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterOptions.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterOptions.cs
@@ -1,8 +1,6 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
-
namespace PdfSharp.Pdf.IO
{
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/AnsiEncoding.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/AnsiEncoding.cs
index 8cba2f9a..eec4f06b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/AnsiEncoding.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/AnsiEncoding.cs
@@ -1,305 +1,314 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
+// Moved to PdfSharp.System
+////// PDFsharp - A .NET library for processing PDF
+////// See the LICENSE file in the solution root for more information.
-using System.Text;
-using PdfSharp.Fonts.Internal;
+////using System.Text;
-namespace PdfSharp.Pdf.Internal
-{
- ///
- /// An encoder use for PDF WinAnsi encoding.
- /// It is by design not to use CodePagesEncodingProvider.Instance.GetEncoding(1252).
- /// However, AnsiEncoding is equivalent to Windows-1252 (CP-1252),
- /// see https://en.wikipedia.org/wiki/Windows-1252
- ///
- public sealed class AnsiEncoding : Encoding
- {
- ///
- /// Gets the byte count.
- ///
- public override int GetByteCount(char[] chars, int index, int count) => count;
+////namespace PdfSharp.Pdf.Internal
+////{
+//// ///
+//// /// An encoder use for PDF WinAnsi encoding.
+//// /// It is by design not to use CodePagesEncodingProvider.Instance.GetEncoding(1252).
+//// /// However, AnsiEncoding is equivalent to Windows-1252 (CP-1252),
+//// /// see https://en.wikipedia.org/wiki/Windows-1252
+//// ///
+//// public sealed class AnsiEncoding : Encoding
+//// {
+//// ///
+//// /// Gets the byte count.
+//// ///
+//// public override int GetByteCount(char[] chars, int index, int count) => count;
- ///
- /// Gets the bytes.
- ///
- public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
- {
- int count = charCount;
- for (; charCount > 0; byteIndex++, charIndex++, charCount--)
- {
- var ch = chars[charIndex];
- bytes[byteIndex] = (byte)UnicodeToAnsi(ch/*, ch*/);
- }
- return count;
- }
+//// ///
+//// /// Gets the bytes.
+//// ///
+//// public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
+//// {
+//// int count = charCount;
+//// for (; charCount > 0; byteIndex++, charIndex++, charCount--)
+//// {
+//// var ch = chars[charIndex];
+//// bytes[byteIndex] = (byte)UnicodeToAnsi(ch/*, ch*/);
+//// }
+//// return count;
+//// }
- ///
- /// Gets the character count.
- ///
- public override int GetCharCount(byte[] bytes, int index, int count) => count;
+//// ///
+//// /// Gets the character count.
+//// ///
+//// public override int GetCharCount(byte[] bytes, int index, int count) => count;
- ///
- /// Gets the chars.
- ///
- public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
- {
- for (int idx = byteCount; idx > 0; byteIndex++, charIndex++, idx--)
- chars[charIndex] = AnsiToUnicode[bytes[byteIndex]];
- return byteCount;
- }
+//// ///
+//// /// Gets the chars.
+//// ///
+//// public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
+//// {
+//// for (int idx = byteCount; idx > 0; byteIndex++, charIndex++, idx--)
+//// chars[charIndex] = AnsiToUnicode[bytes[byteIndex]];
+//// return byteCount;
+//// }
- ///
- /// When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters.
- ///
- /// The number of characters to encode.
- ///
- /// The maximum number of bytes produced by encoding the specified number of characters.
- ///
- public override int GetMaxByteCount(int charCount) => charCount;
+//// ///
+//// /// When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters.
+//// ///
+//// /// The number of characters to encode.
+//// ///
+//// /// The maximum number of bytes produced by encoding the specified number of characters.
+//// ///
+//// public override int GetMaxByteCount(int charCount) => charCount;
- ///
- /// When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes.
- ///
- /// The number of bytes to decode.
- ///
- /// The maximum number of characters produced by decoding the specified number of bytes.
- ///
- public override int GetMaxCharCount(int byteCount) => byteCount;
+//// ///
+//// /// When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes.
+//// ///
+//// /// The number of bytes to decode.
+//// ///
+//// /// The maximum number of characters produced by decoding the specified number of bytes.
+//// ///
+//// public override int GetMaxCharCount(int byteCount) => byteCount;
- ///
- /// Indicates whether the specified Unicode BMP character is available in the ANSI code page 1252.
- ///
- public static bool IsAnsi(char ch)
- {
- if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF')
- return true;
+//// ///
+//// /// Indicates whether the specified Unicode BMP character is available in the ANSI code page 1252.
+//// ///
+//// public static bool IsAnsi(char ch)
+//// {
+//// if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF')
+//// return true;
- return ch switch
- {
- '\u20AC' => true,
- '\u0081' => false,
- '\u201A' => true,
- '\u0192' => true,
- '\u201E' => true,
- '\u2026' => true,
- '\u2020' => true,
- '\u2021' => true,
- '\u02C6' => true,
- '\u2030' => true,
- '\u0160' => true,
- '\u2039' => true,
- '\u0152' => true,
- '\u008D' => false,
- '\u017D' => true,
- '\u008F' => false,
- '\u0090' => false,
- '\u2018' => true,
- '\u2019' => true,
- '\u201C' => true,
- '\u201D' => true,
- '\u2022' => true,
- '\u2013' => true,
- '\u2014' => true,
- '\u02DC' => true,
- '\u2122' => true,
- '\u0161' => true,
- '\u203A' => true,
- '\u0153' => true,
- '\u009D' => false,
- '\u017E' => true,
- '\u0178' => true,
- _ => false
- };
- }
+//// return ch switch
+//// {
+//// '\u20AC' => true,
+//// '\u0081' => false,
+//// '\u201A' => true,
+//// '\u0192' => true,
+//// '\u201E' => true,
+//// '\u2026' => true,
+//// '\u2020' => true,
+//// '\u2021' => true,
+//// '\u02C6' => true,
+//// '\u2030' => true,
+//// '\u0160' => true,
+//// '\u2039' => true,
+//// '\u0152' => true,
+//// '\u008D' => false,
+//// '\u017D' => true,
+//// '\u008F' => false,
+//// '\u0090' => false,
+//// '\u2018' => true,
+//// '\u2019' => true,
+//// '\u201C' => true,
+//// '\u201D' => true,
+//// '\u2022' => true,
+//// '\u2013' => true,
+//// '\u2014' => true,
+//// '\u02DC' => true,
+//// '\u2122' => true,
+//// '\u0161' => true,
+//// '\u203A' => true,
+//// '\u0153' => true,
+//// '\u009D' => false,
+//// '\u017E' => true,
+//// '\u0178' => true,
+//// _ => false
+//// };
+//// }
- ///
- /// Indicates whether the specified string is available in the ANSI code page 1252.
- ///
- public static bool IsAnsi(string s)
- {
- var length = s.Length;
- for (int idx = 0; idx < length; idx++)
- {
- char ch = s[idx];
+//// ///
+//// /// Indicates whether the specified string is available in the ANSI code page 1252.
+//// ///
+//// public static bool IsAnsi(string s)
+//// {
+//// var length = s.Length;
+//// for (int idx = 0; idx < length; idx++)
+//// {
+//// char ch = s[idx];
- if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF')
- continue;
+//// if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF')
+//// continue;
- if (ch switch
- {
- '\u20AC' => true,
- '\u0081' => false, // undefined
- '\u201A' => true,
- '\u0192' => true,
- '\u201E' => true,
- '\u2026' => true,
- '\u2020' => true,
- '\u2021' => true,
- '\u02C6' => true,
- '\u2030' => true,
- '\u0160' => true,
- '\u2039' => true,
- '\u0152' => true,
- '\u008D' => false, // undefined
- '\u017D' => true,
- '\u008F' => false, // undefined
- '\u0090' => false, // undefined
- '\u2018' => true,
- '\u2019' => true,
- '\u201C' => true,
- '\u201D' => true,
- '\u2022' => true,
- '\u2013' => true,
- '\u2014' => true,
- '\u02DC' => true,
- '\u2122' => true,
- '\u0161' => true,
- '\u203A' => true,
- '\u0153' => true,
- '\u009D' => false, // undefined
- '\u017E' => true,
- '\u0178' => true,
- _ => false
- } is false)
- return false;
- }
- return true;
- }
+//// if (ch switch
+//// {
+//// '\u20AC' => true,
+//// '\u0081' => false, // undefined
+//// '\u201A' => true,
+//// '\u0192' => true,
+//// '\u201E' => true,
+//// '\u2026' => true,
+//// '\u2020' => true,
+//// '\u2021' => true,
+//// '\u02C6' => true,
+//// '\u2030' => true,
+//// '\u0160' => true,
+//// '\u2039' => true,
+//// '\u0152' => true,
+//// '\u008D' => false, // undefined
+//// '\u017D' => true,
+//// '\u008F' => false, // undefined
+//// '\u0090' => false, // undefined
+//// '\u2018' => true,
+//// '\u2019' => true,
+//// '\u201C' => true,
+//// '\u201D' => true,
+//// '\u2022' => true,
+//// '\u2013' => true,
+//// '\u2014' => true,
+//// '\u02DC' => true,
+//// '\u2122' => true,
+//// '\u0161' => true,
+//// '\u203A' => true,
+//// '\u0153' => true,
+//// '\u009D' => false, // undefined
+//// '\u017E' => true,
+//// '\u0178' => true,
+//// _ => false
+//// } is false)
+//// return false;
+//// }
+//// return true;
+//// }
- ///
- /// Indicates whether all code points in the specified array are available in the ANSI code page 1252.
- ///
- public static bool IsAnsi(int[] codePoints)
- {
- var length = codePoints.Length;
- for (int idx = 0; idx < length; idx++)
- {
- int ch = codePoints[idx];
+//// ///
+//// /// Indicates whether all code points in the specified array are available in the ANSI code page 1252.
+//// ///
+//// public static bool IsAnsi(int[] codePoints)
+//// {
+//// var length = codePoints.Length;
+//// for (int idx = 0; idx < length; idx++)
+//// {
+//// int ch = codePoints[idx];
- if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF')
- continue;
+//// if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF')
+//// continue;
- // There are 6 values between 128 and 255 that are not part of the original ANSI character set.
- // U+00AD was later added for the soft hyphen. The remaining 5 undefined values (see below) are
- // no valid ANSI characters. All of them are C1 control characters (from U+0080 to U+009F) in
- // Unicode. Therefore, we return false here.
- if (ch switch
- {
- '\u20AC' => true,
- '\u0081' => false, // undefined
- '\u201A' => true,
- '\u0192' => true,
- '\u201E' => true,
- '\u2026' => true,
- '\u2020' => true,
- '\u2021' => true,
- '\u02C6' => true,
- '\u2030' => true,
- '\u0160' => true,
- '\u2039' => true,
- '\u0152' => true,
- '\u008D' => false, // undefined
- '\u017D' => true,
- '\u008F' => false, // undefined
- '\u0090' => false, // undefined
- '\u2018' => true,
- '\u2019' => true,
- '\u201C' => true,
- '\u201D' => true,
- '\u2022' => true,
- '\u2013' => true,
- '\u2014' => true,
- '\u02DC' => true,
- '\u2122' => true,
- '\u0161' => true,
- '\u203A' => true,
- '\u0153' => true,
- '\u009D' => false, // undefined
- '\u017E' => true,
- '\u0178' => true,
- _ => false
- } is false)
- return false;
- }
- return true;
- }
+//// // There are 6 values between 128 and 255 that are not part of the original ANSI character set.
+//// // U+00AD was later added for the soft hyphen. The remaining 5 undefined values (see below) are
+//// // no valid ANSI characters. All of them are C1 control characters (from U+0080 to U+009F) in
+//// // Unicode. Therefore, we return false here.
+//// if (ch switch
+//// {
+//// '\u20AC' => true,
+//// '\u0081' => false, // undefined
+//// '\u201A' => true,
+//// '\u0192' => true,
+//// '\u201E' => true,
+//// '\u2026' => true,
+//// '\u2020' => true,
+//// '\u2021' => true,
+//// '\u02C6' => true,
+//// '\u2030' => true,
+//// '\u0160' => true,
+//// '\u2039' => true,
+//// '\u0152' => true,
+//// '\u008D' => false, // undefined
+//// '\u017D' => true,
+//// '\u008F' => false, // undefined
+//// '\u0090' => false, // undefined
+//// '\u2018' => true,
+//// '\u2019' => true,
+//// '\u201C' => true,
+//// '\u201D' => true,
+//// '\u2022' => true,
+//// '\u2013' => true,
+//// '\u2014' => true,
+//// '\u02DC' => true,
+//// '\u2122' => true,
+//// '\u0161' => true,
+//// '\u203A' => true,
+//// '\u0153' => true,
+//// '\u009D' => false, // undefined
+//// '\u017E' => true,
+//// '\u0178' => true,
+//// _ => false
+//// } is false)
+//// return false;
+//// }
+//// return true;
+//// }
- ///
- /// Maps Unicode to ANSI (CP-1252).
- /// Return an ANSI code in a char or the value of parameter nonAnsi
- /// if Unicode value has no ANSI counterpart.
- ///
- public static char UnicodeToAnsi(char ch, char nonAnsi = '\u003F')
- {
- if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF')
- return ch;
+//// ///
+//// /// Maps Unicode to ANSI (CP-1252).
+//// /// Return an ANSI code in a char or the value of parameter nonAnsi
+//// /// if Unicode value has no ANSI counterpart.
+//// ///
+//// public static char UnicodeToAnsi(char ch, char nonAnsi = '\u003F')
+//// {
+//// if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF')
+//// return ch;
- // Unicode code points from U-0080 to U-009F are no
- // valid ANSI characters in a PDF file.
- // But the 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to
- // themselves. This is the same as .NET handles them.
- return ch switch
- {
- '\u20AC' => '\u0080',
- '\u0081' => '\u0081', // undefined, but in ANSI range.
- '\u201A' => '\u0082',
- '\u0192' => '\u0083',
- '\u201E' => '\u0084',
- '\u2026' => '\u0085',
- '\u2020' => '\u0086',
- '\u2021' => '\u0087',
- '\u02C6' => '\u0088',
- '\u2030' => '\u0089',
- '\u0160' => '\u008A',
- '\u2039' => '\u008B',
- '\u0152' => '\u008C',
- '\u008D' => '\u008D', // undefined, but in ANSI range.
- '\u017D' => '\u008E',
- '\u008F' => '\u008F', // undefined, but in ANSI range.
- '\u0090' => '\u0090', // undefined, but in ANSI range.
- '\u2018' => '\u0091',
- '\u2019' => '\u0092',
- '\u201C' => '\u0093',
- '\u201D' => '\u0094',
- '\u2022' => '\u0095',
- '\u2013' => '\u0096',
- '\u2014' => '\u0097',
- '\u02DC' => '\u0098',
- '\u2122' => '\u0099',
- '\u0161' => '\u009A',
- '\u203A' => '\u009B',
- '\u0153' => '\u009C',
- '\u009D' => '\u009D', // undefined, but in ANSI range.
- '\u017E' => '\u009E',
- '\u0178' => '\u009F',
- _ => nonAnsi
- };
- }
+//// // Unicode code points from U-0080 to U-009F are no
+//// // valid ANSI characters in a PDF file.
+//// // But the 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to
+//// // themselves. This is the same as .NET handles them.
+//// return ch switch
+//// {
+//// '\u20AC' => '\u0080',
+//// '\u0081' => '\u0081', // undefined, but in ANSI range.
+//// '\u201A' => '\u0082',
+//// '\u0192' => '\u0083',
+//// '\u201E' => '\u0084',
+//// '\u2026' => '\u0085',
+//// '\u2020' => '\u0086',
+//// '\u2021' => '\u0087',
+//// '\u02C6' => '\u0088',
+//// '\u2030' => '\u0089',
+//// '\u0160' => '\u008A',
+//// '\u2039' => '\u008B',
+//// '\u0152' => '\u008C',
+//// '\u008D' => '\u008D', // undefined, but in ANSI range.
+//// '\u017D' => '\u008E',
+//// '\u008F' => '\u008F', // undefined, but in ANSI range.
+//// '\u0090' => '\u0090', // undefined, but in ANSI range.
+//// '\u2018' => '\u0091',
+//// '\u2019' => '\u0092',
+//// '\u201C' => '\u0093',
+//// '\u201D' => '\u0094',
+//// '\u2022' => '\u0095',
+//// '\u2013' => '\u0096',
+//// '\u2014' => '\u0097',
+//// '\u02DC' => '\u0098',
+//// '\u2122' => '\u0099',
+//// '\u0161' => '\u009A',
+//// '\u203A' => '\u009B',
+//// '\u0153' => '\u009C',
+//// '\u009D' => '\u009D', // undefined, but in ANSI range.
+//// '\u017E' => '\u009E',
+//// '\u0178' => '\u009F',
+//// _ => nonAnsi
+//// };
+//// }
- ///
- /// Maps WinAnsi to Unicode characters.
- /// The 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to
- /// the C1 control code. This is the same as .NET handles them.
- ///
- static readonly char[] AnsiToUnicode =
- [
- // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- /* 00 */ '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F',
- /* 10 */ '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F',
- /* 20 */ '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F',
- /* 30 */ '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F',
- /* 40 */ '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F',
- /* 50 */ '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F',
- /* 60 */ '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F',
- /* 70 */ '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u007F',
- /* 80 */ '\u20AC', '\u0081', '\u201A', '\u0192', '\u201E', '\u2026', '\u2020', '\u2021', '\u02C6', '\u2030', '\u0160', '\u2039', '\u0152', '\u008D', '\u017D', '\u008F',
- /* 90 */ '\u0090', '\u2018', '\u2019', '\u201C', '\u201D', '\u2022', '\u2013', '\u2014', '\u02DC', '\u2122', '\u0161', '\u203A', '\u0153', '\u009D', '\u017E', '\u0178',
- /* A0 */ '\u00A0', '\u00A1', '\u00A2', '\u00A3', '\u00A4', '\u00A5', '\u00A6', '\u00A7', '\u00A8', '\u00A9', '\u00AA', '\u00AB', '\u00AC', '\u00AD', '\u00AE', '\u00AF',
- /* B0 */ '\u00B0', '\u00B1', '\u00B2', '\u00B3', '\u00B4', '\u00B5', '\u00B6', '\u00B7', '\u00B8', '\u00B9', '\u00BA', '\u00BB', '\u00BC', '\u00BD', '\u00BE', '\u00BF',
- /* C0 */ '\u00C0', '\u00C1', '\u00C2', '\u00C3', '\u00C4', '\u00C5', '\u00C6', '\u00C7', '\u00C8', '\u00C9', '\u00CA', '\u00CB', '\u00CC', '\u00CD', '\u00CE', '\u00CF',
- /* D0 */ '\u00D0', '\u00D1', '\u00D2', '\u00D3', '\u00D4', '\u00D5', '\u00D6', '\u00D7', '\u00D8', '\u00D9', '\u00DA', '\u00DB', '\u00DC', '\u00DD', '\u00DE', '\u00DF',
- /* E0 */ '\u00E0', '\u00E1', '\u00E2', '\u00E3', '\u00E4', '\u00E5', '\u00E6', '\u00E7', '\u00E8', '\u00E9', '\u00EA', '\u00EB', '\u00EC', '\u00ED', '\u00EE', '\u00EF',
- /* F0 */ '\u00F0', '\u00F1', '\u00F2', '\u00F3', '\u00F4', '\u00F5', '\u00F6', '\u00F7', '\u00F8', '\u00F9', '\u00FA', '\u00FB', '\u00FC', '\u00FD', '\u00FE', '\u00FF'
- ];
- }
-}
+//// //// HA_CK for WpfPdfRenderTarget.
+//// //public static char AnsiToUnicodeHack(char ch)
+//// //{
+//// // if (ch < 256)
+//// // return AnsiToUnicode[ch];
+//// // return (char)unchecked((ushort)(short)-1);
+//// //}
+
+
+//// ///
+//// /// Maps WinAnsi to Unicode characters.
+//// /// The 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to
+//// /// the C1 control code. This is the same as .NET handles them.
+//// ///
+//// static readonly char[] AnsiToUnicode =
+//// [
+//// // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
+//// /* 00 */ '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F',
+//// /* 10 */ '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F',
+//// /* 20 */ '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F',
+//// /* 30 */ '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F',
+//// /* 40 */ '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F',
+//// /* 50 */ '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F',
+//// /* 60 */ '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F',
+//// /* 70 */ '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u007F',
+//// /* 80 */ '\u20AC', '\u0081', '\u201A', '\u0192', '\u201E', '\u2026', '\u2020', '\u2021', '\u02C6', '\u2030', '\u0160', '\u2039', '\u0152', '\u008D', '\u017D', '\u008F',
+//// /* 90 */ '\u0090', '\u2018', '\u2019', '\u201C', '\u201D', '\u2022', '\u2013', '\u2014', '\u02DC', '\u2122', '\u0161', '\u203A', '\u0153', '\u009D', '\u017E', '\u0178',
+//// /* A0 */ '\u00A0', '\u00A1', '\u00A2', '\u00A3', '\u00A4', '\u00A5', '\u00A6', '\u00A7', '\u00A8', '\u00A9', '\u00AA', '\u00AB', '\u00AC', '\u00AD', '\u00AE', '\u00AF',
+//// /* B0 */ '\u00B0', '\u00B1', '\u00B2', '\u00B3', '\u00B4', '\u00B5', '\u00B6', '\u00B7', '\u00B8', '\u00B9', '\u00BA', '\u00BB', '\u00BC', '\u00BD', '\u00BE', '\u00BF',
+//// /* C0 */ '\u00C0', '\u00C1', '\u00C2', '\u00C3', '\u00C4', '\u00C5', '\u00C6', '\u00C7', '\u00C8', '\u00C9', '\u00CA', '\u00CB', '\u00CC', '\u00CD', '\u00CE', '\u00CF',
+//// /* D0 */ '\u00D0', '\u00D1', '\u00D2', '\u00D3', '\u00D4', '\u00D5', '\u00D6', '\u00D7', '\u00D8', '\u00D9', '\u00DA', '\u00DB', '\u00DC', '\u00DD', '\u00DE', '\u00DF',
+//// /* E0 */ '\u00E0', '\u00E1', '\u00E2', '\u00E3', '\u00E4', '\u00E5', '\u00E6', '\u00E7', '\u00E8', '\u00E9', '\u00EA', '\u00EB', '\u00EC', '\u00ED', '\u00EE', '\u00EF',
+//// /* F0 */ '\u00F0', '\u00F1', '\u00F2', '\u00F3', '\u00F4', '\u00F5', '\u00F6', '\u00F7', '\u00F8', '\u00F9', '\u00FA', '\u00FB', '\u00FC', '\u00FD', '\u00FE', '\u00FF'
+//// ];
+//// }
+////}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/GlobalObjectTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/GlobalObjectTable.cs
index 34c27a1a..4f6d3838 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/GlobalObjectTable.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/GlobalObjectTable.cs
@@ -1,8 +1,6 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System.Collections.Generic;
-
namespace PdfSharp.Pdf.Internal
{
#if true_
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugItem.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugItem.cs
new file mode 100644
index 00000000..bee7ee86
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugItem.cs
@@ -0,0 +1,40 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.IO;
+
+namespace PdfSharp.Pdf.Internal
+{
+ // TODO Move to new file.
+ ///
+ /// Represents a PDF item literally as a string.
+ /// This class exists for debugging and testing purposes only,
+ /// e.g. to create a non-existing reference for parser testing.
+ /// It is not needed to create valid PDF documents.
+ ///
+ public sealed class PdfDebugItem : PdfPrimitive
+ {
+ // Note that this class does the same as PdfLiteral, but it has a different intended purpose.
+
+ ///
+ /// Creates a new PdfDebugItem from a string.
+ ///
+ public PdfDebugItem(string value)
+ {
+ _value = value;
+ }
+
+ internal override void WriteObject(PdfWriter writer)
+ {
+ var literal = new PdfLiteral(_value);
+ writer.Write(literal);
+ }
+
+ ///
+ /// Returns a string representation of the object.
+ ///
+ public override String ToString() => _value;
+
+ readonly string _value;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugObject.cs
new file mode 100644
index 00000000..463619e7
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugObject.cs
@@ -0,0 +1,40 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.IO;
+
+namespace PdfSharp.Pdf.Internal // #FOLDER Pdf.Internal
+{
+ // TODO Move to new file.
+ ///
+ /// Represents a PDF item literally as a string.
+ /// This class exists for debugging and testing purposes only,
+ /// e.g. to create a non-existing reference for parser testing.
+ /// It is not needed to create valid PDF documents.
+ ///
+ public sealed class PdfDebugObject : PdfPrimitiveObject
+ {
+ ///
+ /// Creates a new PdfRawItem from a raw string.
+ ///
+ public PdfDebugObject(PdfDocument doc, string value)
+ : base(doc, true)
+ {
+ _value = value;
+ }
+
+ internal override void WriteObject(PdfWriter writer)
+ {
+ writer.WriteBeginObject(this);
+ writer.Write(new PdfLiteral(_value));
+ writer.WriteEndObject();
+ }
+
+ ///
+ /// Returns a string representation of the object.
+ ///
+ public override String ToString() => _value;
+
+ readonly string _value;
+ }
+}
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfEncoders.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfEncoders.cs
index 31ad4021..7a6245b4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfEncoders.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfEncoders.cs
@@ -3,6 +3,7 @@
using System.Text;
using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
using PdfSharp.Drawing;
using PdfSharp.Logging;
using PdfSharp.Pdf.Security;
@@ -17,26 +18,39 @@ namespace PdfSharp.Pdf.Internal
public static class PdfEncoders
{
///
- /// Gets the raw encoding.
+ /// Gets the PDFsharp specific encoder RawEncoding.
+ /// Ray encoding allows wo work with string instead of byte array.
+ /// A raw encoded string is equivalent to a byte array of the same length
+ /// where each sting character represents one byte.
+ /// Therefore, each character of a raw string has a value less than 256.
///
public static Encoding RawEncoding => _rawEncoding ??= new RawEncoding();
static Encoding? _rawEncoding;
+ internal static Encoding ByteStringEncoding => _rawEncoding ??= new RawEncoding(); // new name??
+
///
/// Gets the raw Unicode encoding.
///
public static Encoding RawUnicodeEncoding => _rawUnicodeEncoding ??= new RawUnicodeEncoding();
static Encoding? _rawUnicodeEncoding;
+ ///
+ /// Gets the Windows 1252 (ANSI) encoding.
+ ///
+ public static Encoding AnsiEncoding
+ {
+ get => field ??= new AnsiEncoding();
+ }
+
///
/// Gets the Windows 1252 (ANSI) encoding.
///
public static Encoding WinAnsiEncoding
{
- // We consistently use our own WinAnsiEncoding implementation in PDFsharp.
- get => _winAnsiEncoding ??= new AnsiEncoding();
+ //[Obsolete("Use AnsiEncoding")]
+ get => AnsiEncoding;
}
- static Encoding? _winAnsiEncoding;
///
/// Gets the PDF DocEncoding encoding.
@@ -244,7 +258,14 @@ public static string ToHexStringLiteral(byte[]? bytes, bool unicode, bool prefix
public static byte[] FormatStringLiteral(byte[]? bytes, bool unicode, bool prefix, bool hex, PdfStandardSecurityHandler? effectiveSecurityHandler)
{
if (bytes == null || bytes.Length == 0)
- return hex ? [(byte)'<', (byte)'>'] : [(byte)'(', (byte)')'];
+ {
+ byte[] result = hex ? [(byte)'<', (byte)'>'] : [(byte)'(', (byte)')'];
+
+ byte[] test = hex ? "<>"u8.ToArray() : "()"u8.ToArray();
+ Debug.Assert(result == test);
+
+ return result;
+ }
Debug.Assert(!unicode || bytes.Length % 2 == 0, "Odd number of bytes in Unicode string.");
@@ -365,37 +386,37 @@ public static byte[] FormatStringLiteral(byte[]? bytes, bool unicode, bool prefi
for (var idx = 0; idx < count; idx += 2)
{
pdf.Append($"{bytes[idx]:X2}{bytes[idx + 1]:X2}");
- if (idx != 0 && (idx % 48) == 0)
- pdf.Append('\n');
+ //if (idx != 0 && (idx % 48) == 0)
+ // pdf.Append('\n');
}
pdf.Append('>');
}
return RawEncoding.GetBytes(pdf.ToString());
}
- ///
- /// Converts WinAnsi to DocEncode characters. Incomplete, just maps € and some other characters.
- ///
- static byte[] docencode_______ =
- [
- // TODO_OLD: ??? Note: See table in DocEncoding.cs.
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
- 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
- 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
- 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
- 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
- 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
- 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
- 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
- 0xA0, 0x7F, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
- 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x8A, 0x8C, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
- 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
- 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
- 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
- 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
- 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
- 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
- ];
+ /////
+ ///// Converts WinAnsi to DocEncode characters. Incomplete, just maps € and some other characters.
+ /////
+ //static byte[] docencode_______ =
+ //[
+ // // TODO_OLD: ??? See table in DocEncoding.cs.
+ // 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ // 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ // 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+ // 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ // 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ // 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ // 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ // 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ // 0xA0, 0x7F, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ // 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x8A, 0x8C, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ // 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ // 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ // 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ // 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ // 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ // 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+ //];
//public static string DocEncode(string text, bool unicode)//, PdfStandardSecurityHandler securityHandler)
//{
@@ -581,6 +602,7 @@ public static string ToString(XColor color, PdfColorMode colorMode)
///
/// Converts an XMatrix into a string with up to 4 decimal digits and a decimal point.
///
+ [Obsolete]
public static string ToString(XMatrix matrix)
{
const string format = Config.SignificantDecimalPlaces4;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfRawDictionary.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfRawDictionary.cs
new file mode 100644
index 00000000..23e91b72
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfRawDictionary.cs
@@ -0,0 +1,126 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.IO;
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Internal
+{
+ // TODO implementation
+ ///
+ /// Represents a PDF dictionary object xxxxxxxxxxXXXXXXXXXXXXXXXX TODO make ready
+ ///
+ [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
+ public sealed class PdfDebugDictionary : PdfDictionary
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfDebugDictionary()
+ { }
+
+ /////
+ ///// Initializes a new instance of the class.
+ /////
+ //public PdfDebugDictionary(int value)
+ //{ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfDebugDictionary(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfDebugDictionary(PdfDictionary dict)
+ : base(dict)
+ { }
+
+ ///
+ /// Gets or sets the StreamLength of the stream.
+ /// Value is not checked and can be different from the actual length of the stream.
+ ///
+ public int StreamLength { get; set; } // TODO Enforce setting
+
+ ///
+ /// Writes the integer literal.
+ ///
+ internal override void WriteObject(PdfWriter writer)
+ {
+ writer.WriteBeginObject(this);
+
+ if (Stream is not null && writer.EffectiveSecurityHandler != null)
+ {
+ // Encryption could change the size of the stream.
+ // Encrypt the bytes before writing the dictionary to get and update the actual size.
+ var bytes = (byte[])Stream.Value.Clone();
+ writer.EffectiveSecurityHandler.EncryptStream(ref bytes, this);
+ Stream.Value = bytes;
+ }
+ Elements[PdfStream.Keys.Length] = new PdfInteger(StreamLength);
+ var keys = Elements.KeyNames;
+
+#if DEBUG
+ // Sort keys for debugging purposes. Comparing PDF files with for example programs like
+ // Araxis Merge is easier with sorted keys.
+ if (writer.Layout == PdfWriterLayout.Verbose)
+ {
+ var list = new List(keys);
+ list.Sort(PdfName.Comparer);
+ list.CopyTo(keys, 0);
+ }
+#endif
+
+ foreach (var key in keys)
+ WriteDictionaryElement(writer, key);
+ if (Stream != null)
+ WriteDictionaryStream(writer);
+ writer.WriteEndObject();
+ }
+
+ ///
+ /// Gets the DebuggerDisplayAttribute text.
+ ///
+ //string DebuggerDisplay => Invariant($"PdfDebugDictionary({ObjectID.DebuggerDisplay},[{Elements.Count}])={_elements?.DebuggerDisplay}");
+ string DebuggerDisplay => "TODO";
+ }
+}
+
+namespace PdfSharp.Pdf.Internal
+{
+ // TODO implementation
+
+ [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
+ public sealed class PdfDebugArray : PdfArray
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfDebugArray()
+ { }
+
+ /////
+ ///// Initializes a new instance of the class.
+ /////
+ //public PdfDebugDictionary(int value)
+ //{ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfDebugArray(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Gets the DebuggerDisplayAttribute text.
+ ///
+ //string DebuggerDisplay => Invariant($"PdfDebugDictionary({ObjectID.DebuggerDisplay},[{Elements.Count}])={_elements?.DebuggerDisplay}");
+ string DebuggerDisplay => "TODO";
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/RawEncoding.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/RawEncoding.cs
index d1a4769b..c0b0fa72 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/RawEncoding.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/RawEncoding.cs
@@ -3,16 +3,26 @@
using System.Text;
+// v7.0.0 clean up
+
namespace PdfSharp.Pdf.Internal
{
+ // TODO Merge documentation of RawEncoding and mention that C# raw strings is something completely different.
///
/// An encoder for raw strings. The raw encoding is simply the identity relation between
- /// characters and bytes. PDFsharp internally works with raw encoded strings instead of
+ /// characters and bytes. PDFsharp internally often works with raw encoded strings instead of
/// byte arrays because strings are much more handy than byte arrays.
///
///
/// Raw encoded strings represent an array of bytes. Therefore, a character greater than
- /// 255 is not valid in a raw encoded string.
+ /// 255 is not valid in a raw encoded string.
+ ///
+ /// A byte array with n bytes is decoded to a string with n characters, where each character has
+ /// the value of the corresponding byte. I.e. each character has a value from 0 to 255. Its high
+ /// order byte is always 0
+ /// A raw string with n characters is encoded to a byte array with n bytes, where each byte is
+ /// the corresponding character cast to byte. If the high order byte of a character has an illegal
+ /// value other than 0 this value is ignored.
///
public sealed class RawEncoding : Encoding
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/ThreadLocalStorage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/ThreadLocalStorage.cs
index 2cac4ff9..371da79d 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/ThreadLocalStorage.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/ThreadLocalStorage.cs
@@ -88,7 +88,7 @@ public void DetachDocument(PdfDocument.DocumentHandle handle)
///
/// Maps path to document handle.
///
- private readonly Dictionary _importedDocuments = [];
+ readonly Dictionary _importedDocuments = [];
}
}
#endif
\ No newline at end of file
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/DocumentMetadataInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/DocumentMetadataInfo.cs
new file mode 100644
index 00000000..27192989
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/DocumentMetadataInfo.cs
@@ -0,0 +1,120 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.PdfA;
+
+// v7.0.0 Ready
+
+namespace PdfSharp.Pdf.Metadata
+{
+ ///
+ /// A collection of essential metadata from the PDF document.
+ /// All data is formatted as string and can directly be used in the XMP metadata stream.
+ ///
+ public class DocumentMetadataInfo // #Metadata
+ {
+ internal DocumentMetadataInfo(PdfDocument document)
+ {
+ var info = document.Info;
+
+ Title = info.Title;
+ Author = info.Author;
+ Subject = info.Subject;
+ Keywords = info.Keywords;
+ Creator = info.Creator;
+ Producer = info.Producer;
+
+#if DEBUG && true_
+ // Check from where Acrobat takes the text.
+ Title = "(xmp) " + info.Title;
+ Author = "(xmp) " + info.Author;
+ Subject = "(xmp) " + info.Subject;
+ Keywords = "(xmp) " + info.Keywords;
+ Creator = "(xmp) " + info.Creator;
+ Producer = "(xmp) " + info.Producer;
+#endif
+ CreationDate = MetadataManager.ToXmpDateString(info.CreationDate);
+ ModificationDate = MetadataManager.ToXmpDateString(info.ModificationDate);
+ DocumentID = document.Internals.FirstDocumentID;
+ InstanceID = document.Internals.SecondDocumentID;
+ var pdfaManager = PdfAManager.ForDocument(document);
+ PdfAFormat = pdfaManager.IsPdfADocument ? pdfaManager.Format : null;
+ }
+
+ // Reference 2.0: Table 349 — Entries in the document information dictionary / Page 716
+ // The names of the properties comes from the document information dictionary.
+ // See summary for the entries in the document’s metadata.
+
+ ///
+ /// Gets the document’s title.
+ /// Corresponds to “dc:title” entry in the document’s metadata stream.
+ ///
+ public string Title { get; }
+
+ ///
+ /// Gets the name of the person who created the document.
+ /// Corresponds to “dc:creator” entry in the document’s metadata stream.
+ ///
+ public string Author { get; }
+
+ ///
+ /// Gets the name of the subject of the document.
+ /// Corresponds to “dc:description” entry in the document’s metadata stream.
+ ///
+ public string Subject { get; }
+
+ ///
+ /// Gets keywords associated with the document.
+ /// Corresponds to “pdf:Keywords” entry in the document’s metadata stream.
+ ///
+ public string Keywords { get; }
+
+ ///
+ /// If the document was converted to PDF from another format, gets the name of the PDF processor
+ /// that created the original document from which it was converted.
+ /// Corresponds to “xmp:CreatorTool” entry in the document’s metadata stream.
+ ///
+ public string Creator { get; }
+
+ ///
+ /// If the document was converted to PDF from another format, gets the name of the PDF processor
+ /// that converted it to PDF.
+ /// Corresponds to “pdf:Producer” entry in the document’s metadata stream.
+ ///
+ public string Producer { get; }
+
+ ///
+ /// Gets the creation date of the document.
+ /// Can be an empty string.
+ /// Corresponds to “xmp:CreateDate” entry in the document’s metadata stream.
+ ///
+ public string CreationDate { get; }
+
+ ///
+ /// Gets the modification date of the document.
+ /// Can be an empty string.
+ /// Corresponds to “xmp:ModifyDate” entry in the document’s metadata stream.
+ ///
+ public string ModificationDate { get; }
+
+ // Information from trailer.
+
+ ///
+ /// Gets the document ID of the document.
+ /// Corresponds to “xapMM:DocumentID” entry in the document’s metadata stream.
+ ///
+ public string DocumentID { get; }
+
+ ///
+ /// Gets the instance ID of the document.
+ /// Corresponds to “xmpMM:InstanceID” entry in the document’s metadata stream.
+ ///
+ public string InstanceID { get; }
+
+ ///
+ /// Gets the PDF/A part and conformance level, or null,
+ /// if the document is not a PDF/A document.
+ ///
+ public PdfAFormat? PdfAFormat { get; }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/MetadataManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/MetadataManager.cs
new file mode 100644
index 00000000..c1c98a3b
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/MetadataManager.cs
@@ -0,0 +1,159 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Events;
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Internal;
+
+// v7.0.0 Review
+
+namespace PdfSharp.Pdf.Metadata
+{
+ ///
+ /// The MetadataManager provides functionality for easier handling of PDF metadata.
+ ///
+ public class MetadataManager : ManagerBase
+ {
+ ///
+ /// Initializes a new instance of this class.
+ ///
+ MetadataManager(PdfDocument document) : base(document)
+ { }
+
+ ///
+ /// Gets the PDF metadata object from the catalog, or null,
+ /// if no such object exists.
+ ///
+ public PdfMetadata? GetMetadata()
+ {
+ return Document.Catalog.GetMetadata();
+ }
+
+ ///
+ /// Gets or sets the strategy how PDFsharp treads document metadata.
+ ///
+ /// DocumentMetadataStrategy was set more than once.
+ public DocumentMetadataStrategy Strategy
+ {
+ get => _strategy ?? DocumentMetadataStrategy.NoMetadata;
+ set
+ {
+ if (_strategy == value)
+ return;
+
+ if (_strategy != null)
+ {
+ // Update of strategy set by PdfAManager is allowed.
+ if (!(_strategy == DocumentMetadataStrategy.AutoGenerate && value == DocumentMetadataStrategy.UserGenerated))
+ throw new InvalidOperationException("DocumentMetadataStrategy can only be set once.");
+ }
+ _strategy = value;
+ }
+ }
+ DocumentMetadataStrategy? _strategy;
+
+ ///
+ /// Adjusts the document metadata strategy for PDF/A generation if a PDF/A format is set.
+ ///
+ internal void AdjustStrategyForPdfA()
+ {
+ _strategy = _strategy switch
+ {
+ DocumentMetadataStrategy.KeepExisting => Fail(),
+ DocumentMetadataStrategy.NoMetadata => Fail(),
+ DocumentMetadataStrategy.AutoGenerate => DocumentMetadataStrategy.AutoGenerate,
+ DocumentMetadataStrategy.UserGenerated => DocumentMetadataStrategy.UserGenerated,
+ null or DocumentMetadataStrategy.NoMetadata => _strategy = DocumentMetadataStrategy.AutoGenerate,
+ _ => throw new ArgumentOutOfRangeException(nameof(_strategy))
+ };
+ return;
+
+ //[DoesNotReturn]
+ DocumentMetadataStrategy Fail()
+ {
+ throw new InvalidOperationException(
+ $"With a document metadata strategy of '{_strategy}' the document cannot be a PDF/A document.");
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void SetMetadata(byte[] xml)
+ {
+ Document.Catalog.GetOrCreateMetadata().SetMetadata(xml);
+ }
+
+ ///
+ /// Gets the metadata info PDFsharp has collected for the current document.
+ /// This includes PdfDocumentInformation and the document IDs.
+ ///
+ public DocumentMetadataInfo GetMetadataInfo()
+ {
+ var md = new DocumentMetadataInfo(Document);
+ return md;
+ }
+
+ internal void PrepareForSave()
+ {
+ var catalog = Document.Catalog;
+
+ // If HasMetadata is true, but XML stream is empty, then remove the inconsistent Metadata.
+ // We always remove empty Metadata here, regardless of the strategy.
+ //if (catalog.HasMetadata && !(catalog.GetMetadata(false)!.Stream?.Value?.Length > 0))
+ if (!(catalog.GetMetadata()?.Stream?.Value.Length > 0))
+ catalog.Elements.Remove(PdfCatalog.Keys.Metadata);
+
+ if (_strategy is null or DocumentMetadataStrategy.KeepExisting)
+ return;
+
+ if (_strategy == DocumentMetadataStrategy.NoMetadata)
+ {
+ // Remove Metadata.
+ catalog.Elements.Remove(PdfCatalog.Keys.Metadata);
+ return;
+ }
+
+ var metadata = catalog.GetOrCreateMetadata();
+ if (_strategy == DocumentMetadataStrategy.AutoGenerate)
+ {
+ // Create default Metadata if it is currently empty.
+ if (!(catalog.GetMetadata()?.Stream?.Value.Length > 0)) // TODO PdfDictionary.StreamLength would be helpful.
+ {
+ var xml = metadata.CreateDefaultMetadata();
+ metadata.SetMetadata(xml);
+ }
+ return;
+ }
+
+ if (_strategy == DocumentMetadataStrategy.UserGenerated)
+ {
+ var metadataInfo = new DocumentMetadataInfo(Document);
+ var metadataArgs = new DocumentMetadataEventArgs(Document)
+ {
+ Metadata = metadata,
+ Info = metadataInfo
+ };
+ Document.Events.OnCreateDocumentMetadata(Document, metadataArgs);
+ }
+ }
+
+ ///
+ /// Converts a DateTimeOffset in an XMP metadata compatible string.
+ ///
+ internal static string ToXmpDateString(DateTimeOffset? dateTimeOffset)
+ {
+ var result = dateTimeOffset == null
+ ? ""
+ : Invariant($"{dateTimeOffset.Value:yyyy-MM-ddTHH:mm:ssK}");
+ return result;
+ }
+
+ ///
+ /// Gets or creates the MetadataManager for the specified document.
+ ///
+ public static MetadataManager ForDocument(PdfDocument document)
+ => document.MetadataManager ??= new(document);
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/enums/DocumentMetadataStrategy.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/enums/DocumentMetadataStrategy.cs
new file mode 100644
index 00000000..3571e2dd
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/enums/DocumentMetadataStrategy.cs
@@ -0,0 +1,32 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.Metadata
+{
+ ///
+ /// Defines how PDFsharp treads document metadata.
+ ///
+ public enum DocumentMetadataStrategy
+ {
+ ///
+ /// Do not generate metadata.
+ /// Keep the existing metadata of an imported PDF file.
+ ///
+ KeepExisting,
+
+ ///
+ /// Do not generate metadata.
+ ///
+ NoMetadata,
+
+ ///
+ /// Let PDFsharp generate a new metadata object.
+ ///
+ AutoGenerate,
+
+ ///
+ /// Let the user code generate the metadata.
+ ///
+ UserGenerated,
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs
similarity index 84%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs
index f69b1cc8..90ea8f3b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs
@@ -2,25 +2,26 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Pdf.Advanced;
+using System.Collections;
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents the base class for all interactive field dictionaries.
///
- public abstract class PdfAcroField : PdfDictionary
+ public abstract class PdfFormField : PdfDictionary // TODO: FormsCleanUp: Remove namespace and classes.
{
///
- /// Initializes a new instance of PdfAcroField.
+ /// Initializes a new instance of PdfFormField.
///
- internal PdfAcroField(PdfDocument document)
+ internal PdfFormField(PdfDocument document)
: base(document)
{ }
///
- /// Initializes a new instance of the class. Used for type transformation.
+ /// Initializes a new instance of the class. Used for type transformation.
///
- protected PdfAcroField(PdfDictionary dict)
+ protected PdfFormField(PdfDictionary dict)
: base(dict)
{ }
@@ -39,11 +40,11 @@ public string Name
///
/// Gets the field flags of this instance.
///
- public PdfAcroFieldFlags Flags => (PdfAcroFieldFlags)Elements.GetInteger(Keys.Ff);
+ public PdfFormFieldFlags Flags => (PdfFormFieldFlags)Elements.GetInteger(Keys.Ff);
- internal PdfAcroFieldFlags SetFlags
+ internal PdfFormFieldFlags SetFlags
{
- get => (PdfAcroFieldFlags)Elements.GetInteger(Keys.Ff);
+ get => (PdfFormFieldFlags)Elements.GetInteger(Keys.Ff);
set => Elements.SetInteger(Keys.Ff, (int)value);
}
@@ -69,25 +70,25 @@ public virtual PdfItem? Value
///
public bool ReadOnly
{
- get => (Flags & PdfAcroFieldFlags.ReadOnly) != 0;
+ get => (Flags & PdfFormFieldFlags.ReadOnly) != 0;
set
{
if (value)
- SetFlags |= PdfAcroFieldFlags.ReadOnly;
+ SetFlags |= PdfFormFieldFlags.ReadOnly;
else
- SetFlags &= ~PdfAcroFieldFlags.ReadOnly;
+ SetFlags &= ~PdfFormFieldFlags.ReadOnly;
}
}
///
/// Gets the field with the specified name.
///
- public PdfAcroField? this[string name] => GetValue(name);
+ public PdfFormField? this[string name] => GetValue(name);
///
/// Gets a child field by name.
///
- protected virtual PdfAcroField? GetValue(string name)
+ protected virtual PdfFormField? GetValue(string name)
{
if (String.IsNullOrEmpty(name))
return this;
@@ -127,7 +128,7 @@ public string[] GetDescendantNames()
List names = [];
if (HasKids)
{
- PdfAcroFieldCollection fields = Fields;
+ PdfFormFieldCollection fields = Fields;
fields.GetDescendantNames(ref names, null);
}
List temp = [];
@@ -218,7 +219,7 @@ internal virtual void GetDescendantNames(ref List names, string? partial
{
if (HasKids)
{
- PdfAcroFieldCollection fields = Fields;
+ PdfFormFieldCollection fields = Fields;
string t = Elements.GetString(Keys.T);
Debug.Assert(t != "");
if (t.Length > 0)
@@ -247,26 +248,26 @@ internal virtual void GetDescendantNames(ref List names, string? partial
///
/// Gets the collection of fields within this field.
///
- public PdfAcroFieldCollection Fields
+ public PdfFormFieldCollection Fields
{
get
{
if (_fields == null)
{
var o = Elements.GetValue(Keys.Kids, VCF.CreateIndirect);
- _fields = (PdfAcroFieldCollection?)o ?? NRT.ThrowOnNull();
+ _fields = (PdfFormFieldCollection?)o ?? NRT.ThrowOnNull();
}
return _fields;
}
}
- PdfAcroFieldCollection? _fields;
+ PdfFormFieldCollection? _fields;
///
/// Holds a collection of interactive fields.
///
- public sealed class PdfAcroFieldCollection : PdfArray
+ public sealed class PdfFormFieldCollection : PdfArray, IEnumerable
{
- internal PdfAcroFieldCollection(PdfArray array)
+ internal PdfFormFieldCollection(PdfArray array)
: base(array)
{ }
@@ -323,7 +324,7 @@ internal void GetDescendantNames(ref List names, string? partialName)
/// If the actual type cannot be guessed by PDFsharp the function returns an instance
/// of PdfGenericField.
///
- public PdfAcroField this[int index]
+ public PdfFormField this[int index]
{
get
{
@@ -331,7 +332,7 @@ public PdfAcroField this[int index]
Debug.Assert(item is PdfReference);
PdfDictionary? dict = ((PdfReference)item).Value as PdfDictionary;
Debug.Assert(dict != null);
- PdfAcroField? field = dict as PdfAcroField;
+ PdfFormField? field = dict as PdfFormField;
if (field == null && dict != null!)
{
// Do type transformation
@@ -345,9 +346,9 @@ public PdfAcroField this[int index]
///
/// Gets the field with the specified name.
///
- public PdfAcroField? this[string name] => GetValue(name);
+ public PdfFormField? this[string name] => GetValue(name);
- internal PdfAcroField? GetValue(string name)
+ internal PdfFormField? GetValue(string name)
{
if (String.IsNullOrEmpty(name))
return null;
@@ -371,17 +372,17 @@ public PdfAcroField this[int index]
/// If the actual cannot be guessed by PDFsharp the function returns an instance
/// of PdfGenericField.
///
- PdfAcroField CreateAcroField(PdfDictionary dict)
+ PdfFormField CreateAcroField(PdfDictionary dict)
{
string ft = dict.Elements.GetName(Keys.FT);
- PdfAcroFieldFlags flags = (PdfAcroFieldFlags)dict.Elements.GetInteger(Keys.Ff);
+ PdfFormFieldFlags flags = (PdfFormFieldFlags)dict.Elements.GetInteger(Keys.Ff);
switch (ft)
{
case "/Btn":
- if ((flags & PdfAcroFieldFlags.Pushbutton) != 0)
+ if ((flags & PdfFormFieldFlags.Pushbutton) != 0)
return new PdfPushButtonField(dict);
- if ((flags & PdfAcroFieldFlags.Radio) != 0)
+ if ((flags & PdfFormFieldFlags.Radio) != 0)
return new PdfRadioButtonField(dict);
return new PdfCheckBoxField(dict);
@@ -390,7 +391,7 @@ PdfAcroField CreateAcroField(PdfDictionary dict)
return new PdfTextField(dict);
case "/Ch":
- if ((flags & PdfAcroFieldFlags.Combo) != 0)
+ if ((flags & PdfFormFieldFlags.Combo) != 0)
return new PdfComboBoxField(dict);
else
return new PdfListBoxField(dict);
@@ -402,7 +403,65 @@ PdfAcroField CreateAcroField(PdfDictionary dict)
return new PdfGenericField(dict);
}
}
- }
+
+ public new PdfFormFieldEnumerator GetEnumerator()
+ {
+ return new PdfFormFieldEnumerator(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return new PdfFormFieldEnumerator(this);
+ }
+
+ ///
+ /// Enumerates the elements of a .
+ ///
+ public struct PdfFormFieldEnumerator : IEnumerator, IEnumerator, IDisposable
+ {
+ private readonly PdfFormFieldCollection _collection;
+ private int _currentIndex;
+ private PdfFormField? _currentItem;
+
+ internal PdfFormFieldEnumerator(PdfFormFieldCollection fieldCollection)
+ {
+ _collection = fieldCollection;
+ _currentIndex = -1;
+ _currentItem = default;
+ }
+
+ public readonly PdfFormField Current => _currentItem!;
+
+ readonly Object IEnumerator.Current => Current;
+
+ public readonly void Dispose() { }
+
+ public Boolean MoveNext()
+ {
+ if (++_currentIndex >= _collection.Count)
+ {
+ return false;
+ }
+ else
+ {
+ // Gettig the current item using the index will convert
+ // the PdfItem to PdfAcroField for us
+ _currentItem = _collection[_currentIndex];
+ }
+ return true;
+ }
+
+ public void Reset()
+ {
+ _currentIndex = -1;
+ }
+ }
+ }
///
/// Predefined keys of this dictionary.
@@ -440,7 +499,7 @@ public class Keys : KeysBase
///
/// (Optional) An array of indirect references to the immediate children of this field.
///
- [KeyInfo(KeyType.Array | KeyType.Optional, typeof(PdfAcroFieldCollection))]
+ [KeyInfo(KeyType.Array | KeyType.Optional, typeof(PdfFormFieldCollection))]
public const string Kids = "/Kids";
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroForm.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroForm.cs
similarity index 81%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroForm.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroForm.cs
index d1763a9d..d1097078 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroForm.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroForm.cs
@@ -1,43 +1,45 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents an interactive form (or AcroForm), a collection of fields for
/// gathering information interactively from the user.
///
- public sealed class PdfAcroForm : PdfDictionary
+ public sealed class PdfFormForm : PdfDictionary
{
///
/// Initializes a new instance of AcroForm.
///
- internal PdfAcroForm(PdfDocument document)
+ internal PdfFormForm(PdfDocument document)
: base(document)
- {
- _document = document;
- }
+ { }
- internal PdfAcroForm(PdfDictionary dictionary)
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfFormForm(PdfDictionary dictionary)
: base(dictionary)
{ }
///
/// Gets the fields collection of this form.
///
- public PdfAcroField.PdfAcroFieldCollection Fields
+ public PdfFormField.PdfFormFieldCollection Fields
{
get
{
if (_fields == null)
{
var o = Elements.GetValue(Keys.Fields, VCF.CreateIndirect);
- _fields = (PdfAcroField.PdfAcroFieldCollection?)o ?? NRT.ThrowOnNull();
+ _fields = (PdfFormField.PdfFormFieldCollection?)o ?? NRT.ThrowOnNull();
}
return _fields;
}
}
- PdfAcroField.PdfAcroFieldCollection? _fields;
+ PdfFormField.PdfFormFieldCollection? _fields;
///
/// Predefined keys of this dictionary.
@@ -51,7 +53,7 @@ public sealed class Keys : KeysBase
/// (Required) An array of references to the document’s root fields (those with
/// no ancestors in the field hierarchy).
///
- [KeyInfo(KeyType.Array | KeyType.Required, typeof(PdfAcroField.PdfAcroFieldCollection))]
+ [KeyInfo(KeyType.Array | KeyType.Required, typeof(PdfFormField.PdfFormFieldCollection))]
public const string Fields = "/Fields";
///
@@ -100,17 +102,8 @@ public sealed class Keys : KeysBase
///
/// Gets the KeysMeta for these keys.
///
- internal static DictionaryMeta Meta
- {
- get
- {
- if (s_meta == null)
- s_meta = CreateMeta(typeof(Keys));
- return s_meta;
- }
- }
-
- static DictionaryMeta? s_meta;
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
// ReSharper restore InconsistentNaming
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfButtonField.cs
similarity index 86%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfButtonField.cs
index 1982cf1f..9d83aa86 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfButtonField.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using System;
@@ -6,12 +6,12 @@
using System.Diagnostics;
using PdfSharp.Pdf.Annotations;
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents the base class for all button fields.
///
- public abstract class PdfButtonField : PdfAcroField
+ public abstract class PdfButtonField : PdfFormField
{
///
/// Initializes a new instance of the class.
@@ -34,7 +34,7 @@ protected string GetNonOffValue()
{
// Try to get the information from the appearance dictionary.
// Just return the first key that is not /Off.
- // Im not sure what is the right solution to get this value.
+ // I’m not sure what is the right solution to get this value.
var ap = Elements[PdfAnnotation.Keys.AP] as PdfDictionary;
if (ap != null)
{
@@ -51,7 +51,7 @@ protected string GetNonOffValue()
internal override void GetDescendantNames(ref List names, string? partialName)
{
- string t = Elements.GetString(PdfAcroField.Keys.T);
+ string t = Elements.GetString(PdfFormField.Keys.T);
if (t == "")
t = "???";
Debug.Assert(t != "");
@@ -68,7 +68,7 @@ internal override void GetDescendantNames(ref List names, string? partia
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
///
- public new class Keys : PdfAcroField.Keys
+ public new class Keys : PdfFormField.Keys
{
// Pushbuttons have no additional entries.
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfCheckBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfCheckBoxField.cs
similarity index 95%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfCheckBoxField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfCheckBoxField.cs
index 85980bb1..a9456df4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfCheckBoxField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfCheckBoxField.cs
@@ -4,7 +4,7 @@
using PdfSharp.Pdf.Annotations;
using PdfSharp.Pdf.Advanced;
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents the check box field.
@@ -17,9 +17,13 @@ public sealed class PdfCheckBoxField : PdfButtonField
internal PdfCheckBoxField(PdfDocument document)
: base(document)
{
- _document = document;
+ //_document = document; TODO: Correct?
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfCheckBoxField(PdfDictionary dict)
: base(dict)
{ }
@@ -183,14 +187,14 @@ public bool Checked
{
if (!HasKids) //R080317
{
- string value = Elements.GetString(PdfAcroField.Keys.V);
+ string value = Elements.GetString(PdfFormField.Keys.V);
return value.Length != 0 && value != "/Off";
}
else //R080317
{
if (Fields.Elements.Items.Length == 2)
{
- string value = ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.GetString(PdfAcroField.Keys.V);
+ string value = ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.GetString(PdfFormField.Keys.V);
bool bReturn = value.Length != 0 && value != "/Off" && value != "/Nein"; //R081114 (3Std.!!) auch auf Nein prüfen; //TODO_OLD woher kommt der Wert?
return bReturn;
}
@@ -203,7 +207,7 @@ public bool Checked
if (!HasKids)
{
string name = value ? GetNonOffValue() : "/Off";
- Elements.SetName(PdfAcroField.Keys.V, name);
+ Elements.SetName(PdfFormField.Keys.V, name);
Elements.SetName(PdfAnnotation.Keys.AS, name);
}
else
@@ -234,7 +238,7 @@ public bool Checked
}
if (name1.Length != 0)
{
- ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfAcroField.Keys.V, name1);
+ ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfFormField.Keys.V, name1);
((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfAnnotation.Keys.AS, name1);
}
@@ -257,7 +261,7 @@ public bool Checked
}
if (name1.Length != 0)
{
- ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfAcroField.Keys.V, name1);
+ ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfFormField.Keys.V, name1);
((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfAnnotation.Keys.AS, name1);
}
@@ -283,7 +287,7 @@ public bool Checked
}
if (name1.Length != 0)
{
- ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfAcroField.Keys.V, name1);
+ ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfFormField.Keys.V, name1);
((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfAnnotation.Keys.AS, name1);
}
@@ -305,7 +309,7 @@ public bool Checked
}
if (name1.Length != 0)
{
- ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfAcroField.Keys.V, name1);
+ ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfFormField.Keys.V, name1);
((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfAnnotation.Keys.AS, name1);
}
}
@@ -356,7 +360,6 @@ public string UncheckedName
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfChoiceField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfChoiceField.cs
similarity index 97%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfChoiceField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfChoiceField.cs
index dcb24c62..12df21e4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfChoiceField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfChoiceField.cs
@@ -1,12 +1,12 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents the base class for all choice field dictionaries.
///
- public abstract class PdfChoiceField : PdfAcroField
+ public abstract class PdfChoiceField : PdfFormField
{
///
/// Initializes a new instance of the class.
@@ -96,7 +96,7 @@ protected string ValueInOptArray(int index)
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
///
- public new class Keys : PdfAcroField.Keys
+ public new class Keys : PdfFormField.Keys
{
// ReSharper disable InconsistentNaming
@@ -132,7 +132,6 @@ protected string ValueInOptArray(int index)
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
// ReSharper restore InconsistentNaming
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfComboBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfComboBoxField.cs
similarity index 81%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfComboBoxField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfComboBoxField.cs
index a9e4c250..05bfb149 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfComboBoxField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfComboBoxField.cs
@@ -1,7 +1,7 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents the combo box field.
@@ -15,6 +15,10 @@ internal PdfComboBoxField(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfComboBoxField(PdfDictionary dict)
: base(dict)
{ }
@@ -26,7 +30,7 @@ public int SelectedIndex
{
get
{
- string value = Elements.GetString(PdfAcroField.Keys.V);
+ string value = Elements.GetString(PdfFormField.Keys.V);
return IndexInOptArray(value);
}
set
@@ -34,7 +38,7 @@ public int SelectedIndex
if (value != -1)
{
string key = ValueInOptArray(value);
- Elements.SetString(PdfAcroField.Keys.V, key);
+ Elements.SetString(PdfFormField.Keys.V, key);
Elements.SetInteger("/I", value);
}
}
@@ -45,14 +49,14 @@ public int SelectedIndex
///
public override PdfItem? Value
{
- get => Elements[PdfAcroField.Keys.V]!;
+ get => Elements[PdfFormField.Keys.V];
set
{
if (ReadOnly)
throw new InvalidOperationException("The field is read only.");
if (value is PdfString or PdfName)
{
- Elements[PdfAcroField.Keys.V] = value;
+ Elements[PdfFormField.Keys.V] = value;
SelectedIndex = SelectedIndex;
if (SelectedIndex == -1)
{
@@ -75,7 +79,7 @@ public override PdfItem? Value
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
///
- public new class Keys : PdfAcroField.Keys
+ public new class Keys : PdfFormField.Keys
{
// Combo boxes have no additional entries.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfGenericField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfGenericField.cs
similarity index 72%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfGenericField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfGenericField.cs
index 97501e84..f64b2dad 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfGenericField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfGenericField.cs
@@ -1,12 +1,12 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents a generic field. Used for AcroForm dictionaries unknown to PDFsharp.
///
- public sealed class PdfGenericField : PdfAcroField
+ public sealed class PdfGenericField : PdfFormField
{
///
/// Initializes a new instance of PdfGenericField.
@@ -15,6 +15,10 @@ internal PdfGenericField(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfGenericField(PdfDictionary dict)
: base(dict)
{ }
@@ -23,10 +27,9 @@ internal PdfGenericField(PdfDictionary dict)
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
///
- public new class Keys : PdfAcroField.Keys
+ public new class Keys : PdfFormField.Keys
{
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfListBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfListBoxField.cs
similarity index 81%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfListBoxField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfListBoxField.cs
index 789a2488..3dc0ce6d 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfListBoxField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfListBoxField.cs
@@ -1,7 +1,7 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents the list box field.
@@ -15,6 +15,10 @@ internal PdfListBoxField(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfListBoxField(PdfDictionary dict)
: base(dict)
{ }
@@ -40,12 +44,11 @@ public int SelectedIndex
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
///
- public new class Keys : PdfAcroField.Keys
+ public new class Keys : PdfFormField.Keys
{
// List boxes have no additional entries.
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfPushButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfPushButtonField.cs
similarity index 72%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfPushButtonField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfPushButtonField.cs
index 0079fdc7..614fb9e5 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfPushButtonField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfPushButtonField.cs
@@ -1,7 +1,7 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents the push button field.
@@ -14,9 +14,13 @@ public sealed class PdfPushButtonField : PdfButtonField
internal PdfPushButtonField(PdfDocument document)
: base(document)
{
- _document = document;
+ //_document = document; TODO: Correct?
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfPushButtonField(PdfDictionary dict)
: base(dict)
{ }
@@ -25,10 +29,9 @@ internal PdfPushButtonField(PdfDictionary dict)
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
///
- public new class Keys : PdfAcroField.Keys
+ public new class Keys : PdfFormField.Keys
{
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfRadioButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfRadioButtonField.cs
similarity index 55%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfRadioButtonField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfRadioButtonField.cs
index 85ab84a5..133016a9 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfRadioButtonField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfRadioButtonField.cs
@@ -1,7 +1,7 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
/// Represents the radio button field.
@@ -14,9 +14,13 @@ public sealed class PdfRadioButtonField : PdfButtonField
internal PdfRadioButtonField(PdfDocument document)
: base(document)
{
- _document = document;
+ //_document = document; TODO: Correct?
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfRadioButtonField(PdfDictionary dict)
: base(dict)
{ }
@@ -28,7 +32,7 @@ public int SelectedIndex
{
get
{
- string value = Elements.GetString(PdfAcroField.Keys.V);
+ string value = Elements.GetString(PdfFormField.Keys.V);
return IndexInOptStrings(value);
}
set
@@ -36,14 +40,14 @@ public int SelectedIndex
var opt = Elements[Keys.Opt] as PdfArray;
if (opt == null)
- opt = Elements[PdfAcroField.Keys.Kids] as PdfArray;
+ opt = Elements[PdfFormField.Keys.Kids] as PdfArray;
if (opt != null)
{
int count = opt.Elements.Count;
if (value < 0 || value >= count)
throw new ArgumentOutOfRangeException(nameof(value));
- Elements.SetName(PdfAcroField.Keys.V, opt.Elements[value].ToString() ?? NRT.ThrowOnNull());
+ Elements.SetName(PdfFormField.Keys.V, opt.Elements[value].ToString() ?? NRT.ThrowOnNull());
}
}
}
@@ -66,33 +70,32 @@ int IndexInOptStrings(string value)
return -1;
}
- ///
- /// Predefined keys of this dictionary.
- /// The description comes from PDF 1.4 Reference.
- ///
- public new class Keys : PdfButtonField.Keys
- {
///
- /// (Optional; inheritable; PDF 1.4) An array of text strings to be used in
- /// place of the V entries for the values of the widget annotations representing
- /// the individual radio buttons. Each element in the array represents
- /// the export value of the corresponding widget annotation in the
- /// Kids array of the radio button field.
+ /// Predefined keys of this dictionary.
+ /// The description comes from PDF 1.4 Reference.
///
- [KeyInfo(KeyType.Array | KeyType.Optional)]
- public const string Opt = "/Opt";
+ public new class Keys : PdfButtonField.Keys
+ {
+ ///
+ /// (Optional; inheritable; PDF 1.4) An array of text strings to be used in
+ /// place of the V entries for the values of the widget annotations representing
+ /// the individual radio buttons. Each element in the array represents
+ /// the export value of the corresponding widget annotation in the
+ /// Kids array of the radio button field.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Optional)]
+ public const string Opt = "/Opt";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+ }
///
- /// Gets the KeysMeta for these keys.
+ /// Gets the KeysMeta of this dictionary type.
///
- internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
- static DictionaryMeta? _meta;
- }
-
- ///
- /// Gets the KeysMeta of this dictionary type.
- ///
- internal override DictionaryMeta Meta => Keys.Meta;
+ internal override DictionaryMeta Meta => Keys.Meta;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfSignatureField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfSignatureField.cs
new file mode 100644
index 00000000..54e59063
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfSignatureField.cs
@@ -0,0 +1,109 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.IO;
+using PdfSharp.Drawing;
+using PdfSharp.Pdf.Annotations;
+
+namespace PdfSharp.Pdf.OldAcroForms
+{
+ ///
+ /// Represents the signature field.
+ ///
+ public sealed class PdfSignatureField : PdfFormField
+ {
+ ///
+ /// Initializes a new instance of PdfSignatureField.
+ ///
+ internal PdfSignatureField(PdfDocument document)
+ : base(document)
+ {
+ CustomAppearanceHandler = null!;
+ }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfSignatureField(PdfDictionary dict)
+ : base(dict)
+ {
+ CustomAppearanceHandler = null!;
+ }
+
+ ///
+ /// Handler that creates the visual representation of the digital signature in PDF.
+ ///
+ public IAnnotationAppearanceHandler CustomAppearanceHandler { get; internal set; }
+
+ ///
+ /// Creates the custom appearance form X object for the annotation that represents
+ /// this acro form text field.
+ ///
+ void RenderCustomAppearance()
+ {
+ var rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect);
+ if (rect == null)
+ return;
+
+ var visible = rect.X1 + rect.X2 + rect.Y1 + rect.Y2 != 0;
+ if (!visible)
+ return;
+
+ if (CustomAppearanceHandler == null)
+ throw new Exception("AppearanceHandler is not set.");
+
+ var form = new XForm(Document, rect.Size);
+ var gfx = XGraphics.FromForm(form);
+
+ CustomAppearanceHandler.DrawAppearance(gfx, rect.ToXRect());
+
+ form.DrawingFinished();
+
+ // Get existing or create new appearance dictionary
+ if (Elements[PdfAnnotation.Keys.AP] is not PdfDictionary ap)
+ {
+ ap = new PdfDictionary(Document);
+ Elements[PdfAnnotation.Keys.AP] = ap;
+ }
+
+ // Set XRef to normal state
+ ap.Elements["/N"] = form.PdfForm.RequiredReference;
+
+ // PdfRenderer can be null.
+ form.PdfRenderer?.Close();
+ }
+
+ internal override void PrepareForSave()
+ {
+ base.PrepareForSave();
+ if (CustomAppearanceHandler != null!)
+ RenderCustomAppearance();
+ }
+
+ ///
+ /// Predefined keys of this dictionary.
+ /// The description comes from PDF 1.4 Reference.
+ ///
+ public new class Keys : PdfFormField.Keys
+ {
+ ///
+ /// (Optional) The type of PDF object that this dictionary describes; if present,
+ /// must be Sig for a signature dictionary.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Type = "/Type";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfTextField.cs
similarity index 84%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfTextField.cs
index cefef326..ab1cbf63 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfTextField.cs
@@ -6,12 +6,12 @@
using PdfSharp.Pdf.Annotations;
using PdfSharp.Pdf.Internal;
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms // TODO: remove _
{
///
/// Represents the text field.
///
- public sealed class PdfTextField : PdfAcroField
+ public sealed class PdfTextField : PdfFormField
{
///
/// Initializes a new instance of PdfTextField.
@@ -20,6 +20,10 @@ internal PdfTextField(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfTextField(PdfDictionary dict)
: base(dict)
{ }
@@ -29,8 +33,8 @@ internal PdfTextField(PdfDictionary dict)
///
public string Text
{
- get => Elements.GetString(PdfAcroField.Keys.V);
- set { Elements.SetString(PdfAcroField.Keys.V, value); RenderAppearance(); } //HACK_OLD in PdfTextField
+ get => Elements.GetString(PdfFormField.Keys.V);
+ set { Elements.SetString(PdfFormField.Keys.V, value); RenderAppearance(); } //HACK_OLD in PdfTextField
}
///
@@ -63,13 +67,13 @@ public int MaxLength
///
public bool MultiLine
{
- get => (Flags & PdfAcroFieldFlags.Multiline) != 0;
+ get => (Flags & PdfFormFieldFlags.Multiline) != 0;
set
{
if (value)
- SetFlags |= PdfAcroFieldFlags.Multiline;
+ SetFlags |= PdfFormFieldFlags.Multiline;
else
- SetFlags &= ~PdfAcroFieldFlags.Multiline;
+ SetFlags &= ~PdfFormFieldFlags.Multiline;
}
}
@@ -78,13 +82,13 @@ public bool MultiLine
///
public bool Password
{
- get => (Flags & PdfAcroFieldFlags.Password) != 0;
+ get => (Flags & PdfFormFieldFlags.Password) != 0;
set
{
if (value)
- SetFlags |= PdfAcroFieldFlags.Password;
+ SetFlags |= PdfFormFieldFlags.Password;
else
- SetFlags &= ~PdfAcroFieldFlags.Password;
+ SetFlags &= ~PdfFormFieldFlags.Password;
}
}
@@ -97,14 +101,14 @@ void RenderAppearance()
#if true_
PdfFormXObject xobj = new PdfFormXObject(Owner);
Owner.Internals.AddObject(xobj);
- xobj.Elements["/BBox"] = new PdfLiteral("[0 0 122.653 12.707]");
- xobj.Elements["/FormType"] = new PdfLiteral("1");
- xobj.Elements["/Matrix"] = new PdfLiteral("[1 0 0 1 0 0]");
- PdfDictionary res = new PdfDictionary(Owner);
+ xobj.Elements["/BBox"] = new Pdf/Literal("[0 0 122.653 12.707]");
+ xobj.Elements["/FormType"] = new Pdf/Literal("1");
+ xobj.Elements["/Matrix"] = new Pdf/Literal("[1 0 0 1 0 0]");
+ PdfDictionary res = new Pdf/Dictionary(Owner);
xobj.Elements["/Resources"] = res;
- res.Elements["/Font"] = new PdfLiteral("<< /Helv 28 0 R >> /ProcSet [/PDF /Text]");
- xobj.Elements["/Subtype"] = new PdfLiteral("/Form");
- xobj.Elements["/Type"] = new PdfLiteral("/XObject");
+ res.Elements["/Font"] = new Pdf/Literal("<< /Helv 28 0 R >> /ProcSet [/PDF /Text]");
+ xobj.Elements["/Subtype"] = new Pdf/Literal("/Form");
+ xobj.Elements["/Type"] = new Pdf/Literal("/XObject");
string s =
"/Tx BMC " + '\n' +
@@ -186,8 +190,8 @@ void RenderAppearance()
//mdict.Stream.Value = stream;
#else
- var rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect);
- var form = new XForm(_document, rect.Size);
+ var rect = Elements.GetRequiredRectangle(PdfAnnotation.Keys.Rect);
+ var form = new XForm(Document, rect.Size);
var gfx = XGraphics.FromForm(form);
if (BackColor != XColor.Empty)
@@ -204,18 +208,18 @@ void RenderAppearance()
// Get existing or create new appearance dictionary.
if (Elements[PdfAnnotation.Keys.AP] is not PdfDictionary ap)
{
- ap = new PdfDictionary(_document);
+ ap = new PdfDictionary(Document);
Elements[PdfAnnotation.Keys.AP] = ap;
}
// Set XRef to normal state.
- ap.Elements["/N"] = form.PdfForm.Reference;
+ ap.Elements["/N"] = form.PdfForm.RequiredReference;
form.PdfRenderer.Close();
var xobj = form.PdfForm;
string s = xobj.Stream?.ToString() ?? "";
- // Thank you Adobe: Without putting the content in 'EMC brackets'
+ // Without putting the content in 'EMC brackets'
// the text is not rendered by PDF Reader 9 or higher.
s = "/Tx BMC\n" + s + "\nEMC";
if (xobj.Stream != null)
@@ -233,7 +237,7 @@ internal override void PrepareForSave()
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
///
- public new class Keys : PdfAcroField.Keys
+ public new class Keys : PdfFormField.Keys
{
///
/// (Optional; inheritable) The maximum length of the field’s text, in characters.
@@ -245,7 +249,6 @@ internal override void PrepareForSave()
/// Gets the KeysMeta for these keys.
///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
-
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/enums/PdfAcroFieldFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/enums/PdfAcroFieldFlags.cs
similarity index 98%
rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/enums/PdfAcroFieldFlags.cs
rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/enums/PdfAcroFieldFlags.cs
index 22f39e51..b23e6ddd 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/enums/PdfAcroFieldFlags.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/enums/PdfAcroFieldFlags.cs
@@ -1,13 +1,13 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-namespace PdfSharp.Pdf.AcroForms
+namespace PdfSharp.Pdf.OldAcroForms
{
///
- /// Specifies the flags of AcroForm fields.
+ /// Specifies the flags of interactive form (AcroForm) fields.
///
[Flags]
- public enum PdfAcroFieldFlags
+ public enum PdfFormFieldFlags // Table 227
{
// ----- Common to all fields -----------------------------------------------------------------
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormat.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormat.cs
new file mode 100644
index 00000000..94156bc1
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormat.cs
@@ -0,0 +1,55 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.PdfA
+{
+ ///
+ /// Represents the part and conformance level of a PDF/A document.
+ ///
+ [DebuggerDisplay("({this.Name})")]
+ public readonly struct PdfAFormat
+ {
+ internal PdfAFormat(int part, char conformanceLevel)
+ {
+ Part = part;
+ ConformanceLevel = Char.ToUpper(conformanceLevel);
+ }
+
+ ///
+ /// Gets the part number of PDF/A format.
+ /// E.g. 1, 2, 3, or 4.
+ ///
+ public int Part { get; }
+
+ ///
+ /// Gets the level of conformance of PDF/A format.
+ /// E.g. B, A, or U.
+ ///
+ public char ConformanceLevel { get; }
+
+ ///
+ /// Gets readable name of PDF/A format without the prefix PDF/A.
+ /// E.g. '3B'.
+ ///
+ public string Name => Invariant($"{Part}{ConformanceLevel}");
+
+ ///
+ /// Gets readable name of PDF/A format including the prefix PDF/A.
+ /// E.g. 'PDF/A-3B'.
+ ///
+ public string FullName => Invariant($"PDF/A-{Part}{ConformanceLevel}");
+
+ ///
+ /// Returns a string representation of the object.
+ ///
+ public override String ToString() => Part != 0 ? FullName : "(n/a)";
+
+ ///
+ /// Forces to create a format not defined in the class PdfAFormats.
+ /// May be useful for testing only.
+ ///
+ /// Part number of PDF/A format
+ /// Level of conformance of PDF/A format
+ public static PdfAFormat ForcePdfAFormat(int part, char conformanceLevel) => new(part, conformanceLevel);
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormats.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormats.cs
new file mode 100644
index 00000000..cba55baa
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormats.cs
@@ -0,0 +1,74 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// ReSharper disable InconsistentNaming
+
+namespace PdfSharp.Pdf.PdfA
+{
+ ///
+ /// Defines all well-defined PDF/A formats.
+ /// See https://en.wikipedia.org/wiki/PDF/A
+ ///
+ public static class PdfAFormats
+ {
+ ///
+ /// PDF/A-1b – Level B (Basic) conformance.
+ ///
+ public static PdfAFormat PdfA_1a { get; } = new(1, 'a');
+
+ ///
+ /// PDF/A-1a – Level A (Accessible) conformance.
+ ///
+ public static PdfAFormat PdfA_1b { get; } = new(1, 'b');
+
+ ///
+ /// PDF/A-2a – Level A (Accessible) conformance.
+ ///
+ public static PdfAFormat PdfA_2a { get; } = new(2, 'a');
+
+ ///
+ /// PDF/A-2b – Level B (Basic) conformance.
+ ///
+ public static PdfAFormat PdfA_2b { get; } = new(2, 'b');
+
+ ///
+ /// PDF/A-2u – Level U (Unicode) conformance.
+ ///
+ public static PdfAFormat PdfA_2u { get; } = new(2, 'b');
+
+ ///
+ /// PDF/A-3a – Level A (Accessible) conformance.
+ ///
+ public static PdfAFormat PdfA_3a { get; } = new(3, 'a');
+
+ ///
+ /// PDF/A-3b – Level B (Basic) conformance.
+ ///
+ public static PdfAFormat PdfA_3b { get; } = new(3, 'b');
+
+ ///
+ /// PDF/A-3u – Level U (Unicode) conformance.
+ ///
+ public static PdfAFormat PdfA_3u { get; } = new(3, 'u');
+
+ ///
+ /// PDF/A-4a – Level A (Accessible) conformance.
+ ///
+ public static PdfAFormat PdfA_4a { get; } = new(4, 'a');
+
+ ///
+ /// PDF/A-4b – Level B (Basic) conformance.
+ ///
+ public static PdfAFormat PdfA_4b { get; } = new(4, 'b');
+
+ ///
+ /// PDF/A-4f – Level F (Engineering) conformance.
+ ///
+ public static PdfAFormat PdfA_4e { get; } = new(4, 'e');
+
+ ///
+ /// PDF/A-4e – Level E (arbitrary Files) conformance.
+ ///
+ public static PdfAFormat PdfA_4f { get; } = new(4, 'f');
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAManager.cs
new file mode 100644
index 00000000..e241660e
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAManager.cs
@@ -0,0 +1,102 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// v7.0.0 review
+
+using PdfSharp.Pdf.Internal;
+
+namespace PdfSharp.Pdf.PdfA
+{
+ ///
+ /// The PdfAManager bundles PDF/A specific functionality of a PDF document.
+ ///
+ public class PdfAManager : ManagerBase
+ {
+ // TODOs:
+ // Handle PDF/A for read documents.
+ // What else is missing?
+
+ ///
+ /// Initialized a new instance of this class for the specified document
+ ///
+ ///
+ PdfAManager(PdfDocument document) : base(document)
+ {
+ Initialize();
+ }
+
+ ///
+ /// Sets PDF/A part and conformance level for the document.
+ /// After once set it cannot be changed.
+ ///
+ public void SetFormat(PdfAFormat format)
+ {
+ if (_formatSet)
+ {
+ throw new InvalidOperationException(
+ "PDF/A format of this document is already set and cannot be changed anymore.");
+ }
+
+ if (!Document.IsImported)
+ {
+ // PDF document is newly created.
+ if (Document.PageCount > 0)
+ {
+ throw new InvalidOperationException(
+ "For a newly created document PDF/A settings must be done before any PDF content is created " +
+ "to ensure that all pages are PDF/A compatible.");
+ }
+ }
+ // PDF/a requires document metadata.
+ Document.GetMetadataManager().AdjustStrategyForPdfA();
+
+ Part = format.Part;
+ Level = format.ConformanceLevel;
+ _formatSet = true;
+ }
+ bool _formatSet;
+
+ ///
+ /// Return true if the PDF document is a PDF/A document, false otherwise.
+ ///
+ public bool IsPdfADocument => Part != 0;
+
+ ///
+ /// Gets the part number of a PDF/A document, or 0, if the document is
+ /// not a PDF/A document.
+ ///
+ public int Part { get; private set; }
+
+ ///
+ /// Gets the conformance level of a PDF/A document, or ' ' (blank), if the document is
+ /// not a PDF/A document.
+ ///
+ public char Level { get; private set; } = ' ';
+
+ ///
+ /// Gets the current PdfAFormat of a PDF/A document.
+ ///
+ public PdfAFormat Format => new(Part, Level);
+
+ ///
+ /// Gets or creates the PdfAManager for the specified document.
+ ///
+ public static PdfAManager ForDocument(PdfDocument document)
+ => document.PdfAManager ??= new(document);
+
+ void Initialize()
+ {
+ Document.EnsureNotDisposed();
+ if (!_initialized)
+ {
+ _initialized = true;
+ Document.EnsureNotYetSaved();
+ if (Document.IsImported)
+ {
+ // TODO: Get PDF/A conformance
+ }
+ }
+ }
+ bool _initialized;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionBase.cs
index c7e59ef6..0b9964e9 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionBase.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionBase.cs
@@ -51,7 +51,6 @@ protected bool HandleCryptographicExceptionOnDecryption()
"If PDFsharp will load the file and the contents seem to be correct, the file is at least partly not encrypted as expected.");
return true;
}
-
return false;
}
@@ -71,9 +70,9 @@ protected bool HandleCryptographicExceptionOnDecryption()
public int? RevisionValue { get; protected set; }
- public int? LengthValue { get; protected set; }
-
- public int? ActualLength { get; protected set; }
+ // Due to PDF reference length has not always to be set. But the adobe powered PDF viewer extension for edge cannot open files correctly, if length is missing.
+ // So we always output a length value.
+ public int LengthValue { get; protected set; }
public bool EncryptMetadata { get; protected set; }
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs
index 4b6b1c05..c809387c 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs
@@ -2,6 +2,9 @@
// See the LICENSE file in the solution root for more information.
using System.Security.Cryptography;
+using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
+using PdfSharp.Logging;
using PdfSharp.Pdf.Internal;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.IO;
@@ -21,9 +24,9 @@ public PdfEncryptionV1To4(PdfStandardSecurityHandler securityHandler) : base(sec
///
public void SetEncryptionToV1()
{
- Initialize(1);
+ Initialize(1, 40);
SecurityHandler.RemoveCryptFilters();
- SecurityHandler._document.SetRequiredVersion(12);
+ SecurityHandler.Document.SetRequiredVersion(12);
}
///
@@ -34,7 +37,7 @@ public void SetEncryptionToV2(int length = 40)
{
Initialize(2, length);
SecurityHandler.RemoveCryptFilters();
- SecurityHandler._document.SetRequiredVersion(14);
+ SecurityHandler.Document.SetRequiredVersion(14);
}
///
@@ -44,9 +47,9 @@ public void SetEncryptionToV2(int length = 40)
// ReSharper disable once InconsistentNaming
public void SetEncryptionToV4UsingRC4(bool encryptMetadata = true)
{
- Initialize(4, null, encryptMetadata);
- SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToRC4ForV4(ActualLength!.Value);
- SecurityHandler._document.SetRequiredVersion(15);
+ Initialize(4, 128, encryptMetadata);
+ SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToRC4ForV4();
+ SecurityHandler.Document.SetRequiredVersion(15);
}
///
@@ -55,9 +58,9 @@ public void SetEncryptionToV4UsingRC4(bool encryptMetadata = true)
/// The document metadata stream shall be encrypted (default: true).
public void SetEncryptionToV4UsingAES(bool encryptMetadata = true)
{
- Initialize(4, null, encryptMetadata);
+ Initialize(4, 128, encryptMetadata);
SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToAESForV4();
- SecurityHandler._document.SetRequiredVersion(16);
+ SecurityHandler.Document.SetRequiredVersion(16);
}
///
@@ -67,20 +70,25 @@ public override void InitializeFromLoadedSecurityHandler()
{
VersionValue = SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.V);
RevisionValue = SecurityHandler.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R);
- LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : null;
+ LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : GetDefaultLength();
- UpdateActualLength();
-
- EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true.
+ // TODO GetBoolean(PdfStandardSecurityHandler.Keys.EncryptMetadata, true)
+ EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. // #US373
CheckVersionAndLength(VersionValue, LengthValue);
var calculatedRevision = CalculateRevisionValue(VersionValue.Value, SecurityHandler.GetCorrectedPermissionsValue());
+
if (calculatedRevision != RevisionValue)
- Debug.Assert(calculatedRevision == RevisionValue);
+ {
+#pragma warning disable CA2254
+ PdfSharpLogHost.Logger.LogError($"Security Handler: Found revision {RevisionValue}, but expected revision {calculatedRevision}.");
+#pragma warning restore CA2254
+ // Don’t correct the revision value, as the wrong revision value was probably used to calculate the password hashes written in the file.
+ }
}
- void Initialize(int versionValue, int? lengthValue = null, bool encryptMetadata = true)
+ void Initialize(int versionValue, int lengthValue, bool encryptMetadata = true)
{
CheckVersionAndLength(versionValue, lengthValue);
@@ -88,30 +96,28 @@ void Initialize(int versionValue, int? lengthValue = null, bool encryptMetadata
RevisionValue = null; // Revision is calculated later in PrepareEncryptionForSaving().
LengthValue = lengthValue;
- UpdateActualLength();
-
EncryptMetadata = encryptMetadata;
}
- void UpdateActualLength()
+ int GetDefaultLength()
{
- ActualLength = VersionValue switch
+ return VersionValue switch
{
- 1 => 40,
- 2 => LengthValue ?? 40, // The default for Length value is 40.
- 4 => 128,
+ 1 => 40, // Always 40.
+ 2 => 40, // Default value.
+ 4 => 128, // Always 128.
_ => throw TH.InvalidOperationException_InvalidVersionValueForEncryptionVersion1To4()
};
}
- static void CheckVersionAndLength(int? versionValue, int? lengthValue)
+ static void CheckVersionAndLength(int? versionValue, int lengthValue)
{
if (!IsVersionSupported(versionValue))
throw TH.InvalidOperationException_InvalidVersionValueForEncryptionVersion1To4();
if (versionValue == 2) // Length is only needed for V2.
{
- if (lengthValue is null or < 40 or > 128 || lengthValue % 8 > 0)
+ if (lengthValue is < 40 or > 128 || lengthValue % 8 > 0)
throw TH.InvalidOperationException_InvalidKeyLengthForEncryptionVersion2();
}
}
@@ -135,10 +141,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne
CheckVersionAndLength(VersionValue, LengthValue);
SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.V, VersionValue!.Value);
- if (LengthValue.HasValue)
- SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.Length, LengthValue.Value);
- else
- SecurityHandler.Elements.Remove(PdfSecurityHandler.Keys.Length);
+ SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.Length, LengthValue);
var permissionsValue = SecurityHandler.GetCorrectedPermissionsValue();
SecurityHandler.Elements.SetInteger(PdfStandardSecurityHandler.Keys.P, (int)permissionsValue);
@@ -153,7 +156,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne
{
SecurityHandler.Elements.SetBoolean(PdfStandardSecurityHandler.Keys.EncryptMetadata, EncryptMetadata);
- var metadata = SecurityHandler._document.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata);
+ var metadata = SecurityHandler.Document.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata, VCF.Create);
if (metadata is null)
throw TH.InvalidOperationException_CouldNotFindMetadataDictionary();
@@ -167,7 +170,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne
Debug.Assert(ownerPassword.Length > 0, "Empty owner password.");
- var documentId = PdfEncoders.RawEncoding.GetBytes(SecurityHandler._document.Internals.FirstDocumentID);
+ var documentId = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Document.Internals.FirstDocumentID);
var (userValueArray, ownerValueArray) = ComputeOwnerAndUserValues(userPassword, ownerPassword, documentId, permissionsValue);
@@ -242,7 +245,7 @@ byte[] ComputeOwnerValue(byte[] userPad, byte[] ownerPad)
if (RevisionValue >= 3)
{
// The encryption key length (in bytes) shall depend on the Length value (in bits).
- var keyLength = ActualLength!.Value / 8;
+ var keyLength = LengthValue / 8;
// Hash the pad 50 times
for (var idx = 0; idx < 50; idx++)
@@ -308,7 +311,7 @@ byte[] ComputeUserValueByEncryptionKey(byte[] documentId)
userValue[idx] = 0;
// Create encryption key with the specified length.
- var keyLength = ActualLength!.Value / 8;
+ var keyLength = LengthValue / 8;
Debug.Assert(keyLength == _globalEncryptionKey.Length);
var encryptionKey = new Byte[keyLength];
@@ -368,9 +371,9 @@ void ComputeAndStoreEncryptionKey(byte[] documentId, byte[] paddedPassword, byte
if (RevisionValue >= 3)
{
// The encryption and MD5 hashing key length (in bytes) shall depend on the Length value (in bits).
- keyLength = ActualLength!.Value / 8;
+ keyLength = LengthValue / 8;
-#if !NET6_0_OR_GREATER
+#if !NET8_0_OR_GREATER
// We have to call Initialize here for .NET 4.6.2.
// .NET 6/8 include Initialize in "_md5.TransformFinalBlock()".
_md5.Initialize();
@@ -405,8 +408,7 @@ void ComputeAndStoreEncryptionKey(byte[] documentId, byte[] paddedPassword, byte
public override PasswordValidity ValidatePassword(string inputPassword)
{
VersionValue = SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.V);
- LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : null;
- UpdateActualLength();
+ LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : GetDefaultLength();
CheckVersionAndLength(VersionValue, LengthValue);
RevisionValue = SecurityHandler.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R);
@@ -414,7 +416,7 @@ public override PasswordValidity ValidatePassword(string inputPassword)
var userValue = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Elements.GetString(PdfStandardSecurityHandler.Keys.U));
var ownerValue = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Elements.GetString(PdfStandardSecurityHandler.Keys.O));
var permissionsValue = SecurityHandler.Elements.GetUnsignedInteger(PdfStandardSecurityHandler.Keys.P);
- EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true.
+ EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. // #US373
var documentId = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Owner.Internals.FirstDocumentID);
@@ -447,7 +449,7 @@ bool ValidateOwnerPassword(byte[] documentId, string inputPassword, byte[] userV
if (RevisionValue >= 3)
{
// The encryption key length (in bytes) shall depend on the Length value (in bits).
- var keyLength = ActualLength!.Value / 8;
+ var keyLength = LengthValue / 8;
// Hash the pad 50 times.
for (var idx = 0; idx < 50; idx++)
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs
index ea695f06..bef96ca8 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs
@@ -2,13 +2,12 @@
// See the LICENSE file in the solution root for more information.
using System.Numerics;
-using PdfSharp.Pdf.IO;
-using System.Security.Cryptography;
using System.Text;
+using System.Security.Cryptography;
+using PdfSharp.Pdf.IO;
using PdfSharp.Internal;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.Internal;
-using System.Runtime.CompilerServices;
namespace PdfSharp.Pdf.Security.Encryption
{
@@ -19,7 +18,7 @@ class PdfEncryptionV5 : PdfEncryptionBase
{
public PdfEncryptionV5(PdfStandardSecurityHandler securityHandler) : base(securityHandler)
{
- SecurityHandler._document.SetRequiredVersion(20);
+ SecurityHandler.Document.SetRequiredVersion(20);
}
///
@@ -28,27 +27,27 @@ public PdfEncryptionV5(PdfStandardSecurityHandler securityHandler) : base(securi
/// True, if the document metadata stream shall be encrypted (default: true).
public void Initialize(bool encryptMetadata = true)
{
- VersionValue = 5; // Always 5 for PdfEncryptionV5.
+ VersionValue = 5; // Always 5 for PdfEncryptionV5.
RevisionValue = 6; // Always 6 for PdfEncryptionV5.
- LengthValue = null; // Deprecated in PDF 2.0.
-
- ActualLength = 256; // Always 256 for PdfEncryptionV5.
-
+ LengthValue = GetDefaultLength(); // Deprecated in PDF 2.0. But the adobe powered PDF viewer extension for edge cannot open files correctly, if length is missing.
+
EncryptMetadata = encryptMetadata;
SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToAESForV5();
}
+ int GetDefaultLength() => 256; // Always 256 for PdfEncryptionV5.
+
///
/// Initializes the PdfEncryptionV5 with the values that were saved in the security handler.
///
public override void InitializeFromLoadedSecurityHandler()
{
- var encryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true.
+ var encryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. // #US373
Initialize(encryptMetadata);
}
- static void CheckValues(int? versionValue, int? revisionValue, int? lengthValue)
+ static void CheckValues(int? versionValue, int? revisionValue, int lengthValue)
{
if (versionValue is not 5)
throw TH.InvalidOperationException_InvalidVersionValueForEncryptionVersion5();
@@ -56,7 +55,7 @@ static void CheckValues(int? versionValue, int? revisionValue, int? lengthValue)
if (revisionValue is not (5 or 6))
throw TH.InvalidOperationException_InvalidRevisionValueForEncryptionVersion5();
- if (lengthValue is not (null or 256))
+ if (lengthValue is not 256)
throw TH.InvalidOperationException_InvalidLengthValueForEncryptionVersion5();
}
@@ -179,7 +178,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne
SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.V, VersionValue!.Value);
// In PDF reference, Length is marked as deprecated in PDF 2.0, but Adobe Reader cannot read V5 encrypted files if this key is omitted.
- SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.Length, ActualLength!.Value);
+ SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.Length, LengthValue);
var permissionsValue = SecurityHandler.GetCorrectedPermissionsValue();
SecurityHandler.Elements.SetInteger(PdfStandardSecurityHandler.Keys.P, (int)permissionsValue);
@@ -195,7 +194,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne
{
SecurityHandler.Elements.SetBoolean(PdfStandardSecurityHandler.Keys.EncryptMetadata, EncryptMetadata);
- var metadata = SecurityHandler._document.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata);
+ var metadata = SecurityHandler.Document.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata, VCF.Create);
if (metadata is null)
throw TH.InvalidOperationException_CouldNotFindMetadataDictionary();
@@ -233,7 +232,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne
void CreateAndStoreEncryptionKey()
{
// The file encryption key shall be a 256-bit (32-byte) value generated with a strong random number generator.
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
_encryptionKey = RandomNumberGenerator.GetBytes(32);
#else
using var cryptoProvider = new RNGCryptoServiceProvider();
@@ -277,7 +276,7 @@ static byte[] CreateUtf8Password(string password)
(byte[] UserValue, byte[] UserEValue) ComputeUserValues(byte[] utf8InputPassword)
{
// a) Generate random bytes for user validation salt and user key salt.
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
var validationSalt = RandomNumberGenerator.GetBytes(8);
var keySalt = RandomNumberGenerator.GetBytes(8);
#else
@@ -314,7 +313,7 @@ static byte[] CreateUtf8Password(string password)
(byte[] OwnerValue, byte[] OwnerEValue) ComputeOwnerValues(byte[] utf8InputPassword, byte[] userValue)
{
// a) Generate random bytes for owner validation salt and owner key salt.
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
var validationSalt = RandomNumberGenerator.GetBytes(8);
var keySalt = RandomNumberGenerator.GetBytes(8);
#else
@@ -377,7 +376,7 @@ byte[] ComputeHashInternal(byte[] password, byte[] salt, bool computeOwnerHash,
var input = password.Concat(salt);
if (computeOwnerHash)
input = input.Concat(userValue!); // Shall not be null, if computeOwnerHash is true.
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
var k = SHA256.HashData(input.ToArray());
#else
var sha = SHA256.Create();
@@ -412,15 +411,15 @@ byte[] ComputeHashInternal(byte[] password, byte[] salt, bool computeOwnerHash,
// c) + d): Take the first 16 bytes of e as an unsigned big-endian integer.
var e16 = e[..16];
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
var e16BigEndianUnsigned = new BigInteger(e16, true, true);
#else
- var e16BigEndianUnsigned = DotNetHelper.CreateBigInteger(e16, true, true);
+ var e16BigEndianUnsigned = BigInteger.CreateBigInteger(e16, true, true);
#endif
- // Calculate the remainder of the result by modulo 3
- // and according to that result choose the SHA algorithm to calculate the new k from e.
+ // Calculate the remainder of the result by modulo 3 and according to that result
+ // choose the SHA algorithm to calculate the new k from e.
BigInteger.DivRem(e16BigEndianUnsigned, 3, out var remainder);
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
if (remainder == 0)
k = SHA256.HashData(e);
else if (remainder == 1)
@@ -454,7 +453,7 @@ static byte[] ComputeHashRevision5Internal(byte[] password, byte[] salt, bool co
var input = password.Concat(salt);
if (computeOwnerHash)
input = input.Concat(userValue!); // Shall not be null, if computeOwnerHash is true.
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
var k = SHA256.HashData(input.ToArray());
#else
var sha = SHA256.Create();
@@ -494,7 +493,7 @@ byte[] ComputePermsValue(uint pValue)
perms[11] = (byte)'b';
// e) Set bytes 12-15 to 4 bytes of random data, which will be ignored.
-#if NET6_0_OR_GREATER
+#if NET8_0_OR_GREATER
var randomData = RandomNumberGenerator.GetBytes(4);
#else
var randomData = new byte[4];
@@ -521,7 +520,7 @@ public override PasswordValidity ValidatePassword(string inputPassword)
{
VersionValue = SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.V);
RevisionValue = SecurityHandler.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R);
- LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : null;
+ LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : GetDefaultLength();
// Ensure properties are set to the only valid values for PdfEncryptionV5.
CheckValues(VersionValue, RevisionValue, LengthValue);
@@ -535,7 +534,7 @@ public override PasswordValidity ValidatePassword(string inputPassword)
var permissionsValue = SecurityHandler.Elements.GetUnsignedInteger(PdfStandardSecurityHandler.Keys.P);
var permsValue = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Elements.GetString(PdfStandardSecurityHandler.Keys.Perms));
- EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true.
+ EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. // #US373
// 7.6.4.3.3 a) - b): Create UTF-8 password.
var utf8InputPassword = CreateUtf8Password(inputPassword);
@@ -703,7 +702,7 @@ bool ValidatePermissions(byte[] permsValue, uint pValue)
return false;
// Bytes 0-3 of the decrypted Perms entry, treated as a little-endian integer, are the user permissions. They should match the value in the P key.
-#if NET6_0_OR_GREATER || USE_INDEX_AND_RANGE_
+#if NET8_0_OR_GREATER || USE_INDEX_AND_RANGE_
var pFromPerms = BitConverter.ToUInt32(permsDecrypted[..4]); // Little-endian is default, so we don’t have to change the order.
#else
var pFromPerms = BitConverter.ToUInt32(permsDecrypted.Take(4).ToArray(), 0); // Little-endian is default, so we don’t have to change the order.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/CryptFilterBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/CryptFilterBase.cs
index 787a06f5..81891bc5 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/CryptFilterBase.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/CryptFilterBase.cs
@@ -22,12 +22,14 @@ protected CryptFilterBase(PdfDictionary dict) : base(dict)
{ }
///
- /// Encrypts the given bytes. Returns true if the crypt filter encrypted the bytes, or false, if the security handler shall do it.
+ /// Encrypts the given bytes. Returns true if the crypt filter encrypted the bytes,
+ /// or false, if the security handler shall do it.
///
internal abstract bool EncryptForEnteredObject(ref byte[] bytes);
///
- /// Decrypts the given bytes. Returns true if the crypt filter decrypted the bytes, or false, if the security handler shall do it.
+ /// Decrypts the given bytes. Returns true if the crypt filter decrypted the bytes,
+ /// or false, if the security handler shall do it.
///
internal abstract bool DecryptForEnteredObject(ref byte[] bytes);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/IdentityCryptFilter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/IdentityCryptFilter.cs
index 2ce2e27f..4a15097d 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/IdentityCryptFilter.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/IdentityCryptFilter.cs
@@ -1,15 +1,28 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
namespace PdfSharp.Pdf.Security
{
///
/// Represents the identity crypt filter, which shall be provided by a PDF processor and pass the data unchanged.
///
- class IdentityCryptFilter : CryptFilterBase
+ public class IdentityCryptFilter : CryptFilterBase
{
+ public IdentityCryptFilter()
+ { }
+
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal IdentityCryptFilter(PdfDictionary dict)
+ : base(dict)
+ { }
+
internal static IdentityCryptFilter Instance { get; } = new();
-
+
///
/// Encrypts the given bytes. Returns true if the crypt filter encrypted the bytes, or false, if the security handler shall do it.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/MD5Managed.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/MD5Managed.cs
index 127c2f71..dccc58db 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/MD5Managed.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/MD5Managed.cs
@@ -23,6 +23,7 @@
// a security warning and PDFsharp does not work for users that must use code that is FIPS
// compliant. We also need the code for PDFsharp running on Blazor and other platforms
// where the .NET implementation lacks of the retired MD5 class.
+// Also used in PdfEmbeddedFileStream to calculate the value of /CheckSum.
using System.Security.Cryptography;
@@ -48,6 +49,14 @@ class MD5Managed : HashAlgorithm
public new static MD5Managed Create() => new();
+ public static string ComputeHashHex(byte[] bytes)
+ {
+ var md5 = Create();
+ var hash = md5.ComputeHash(bytes);
+ var hex = BitConverter.ToString(hash).Replace("-", "");
+ return hex;
+ }
+
public sealed override void Initialize()
{
_data = new byte[64];
@@ -56,7 +65,7 @@ public sealed override void Initialize()
_abcd = new()
{
// Initialize values as defined in RFC 1321.
- // Note: The code below may look strange but is correct.
+ // Note that the code below may look strange but is correct.
A = A,
B = B,
C = C,
@@ -104,7 +113,7 @@ protected override byte[] HashFinal()
MD5Core.ABCD _abcd = new()
{
// Initialize values as defined in RFC 1321.
- // Note: The code below may look strange but is correct.
+ // Note that the code below may look strange but is correct.
A = A,
B = B,
C = C,
@@ -125,7 +134,7 @@ public static byte[] GetHash(byte[] input)
throw new ArgumentNullException(nameof(input));
// Initialize values defined in RFC 1321.
- // Note: The code below may look strange but is correct.
+ // Note that the code below may look strange but is correct.
var abcd = new ABCD
{
A = A,
@@ -153,7 +162,7 @@ internal static byte[] GetHashFinalBlock(byte[] input, int ibStart, int cbSize,
byte[] length = BitConverter.GetBytes(len);
// Padding is a single bit 1, followed by the number of 0s required to make size congruent to 448 modulo 512. Step 1 of RFC 1321
- // The CLR ensures that our buffer is 0-assigned, we don't need to explicitly set it. This is why it ends up being quicker to just
+ // The CLR ensures that our buffer is 0-assigned, we don’t need to explicitly set it. This is why it ends up being quicker to just
// use a temporary array rather than doing in-place assignment (5% for small inputs).
Array.Copy(input, ibStart, working, 0, cbSize);
working[cbSize] = 0x80;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilter.cs
index d36d13d4..6c0ae4fb 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilter.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilter.cs
@@ -18,9 +18,13 @@ public class PdfCryptFilter : CryptFilterBase
public PdfCryptFilter(PdfStandardSecurityHandler? parentStandardSecurityHandler)
{
Initialize(parentStandardSecurityHandler);
- _parentStandardSecurityHandler?._document.SetRequiredVersion(15);
+ _parentStandardSecurityHandler?.Document.SetRequiredVersion(15);
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfCryptFilter(PdfDictionary dict) : base(dict)
{ }
@@ -58,7 +62,7 @@ public void SetEncryptionToRC4ForV4(int length = 128)
public void SetEncryptionToAESForV4()
{
Initialize(CryptFilterMethod.AESV2, 128);
- _parentStandardSecurityHandler?._document.SetRequiredVersion(16);
+ _parentStandardSecurityHandler?.Document.SetRequiredVersion(16);
}
///
@@ -68,7 +72,7 @@ public void SetEncryptionToAESForV4()
public void SetEncryptionToAESForV5()
{
Initialize(CryptFilterMethod.AESV3, 256);
- _parentStandardSecurityHandler?._document.SetRequiredVersion(20);
+ _parentStandardSecurityHandler?.Document.SetRequiredVersion(20);
}
void Initialize(CryptFilterMethod method, int lengthValue = 40)
@@ -218,19 +222,19 @@ internal override bool DecryptForEnteredObject(ref byte[] bytes)
void SetCryptFilterMethod(CryptFilterMethod cryptFilterMethod)
{
_cryptFilterMethod = cryptFilterMethod;
-#if NET6_0_OR_GREATER
- Elements.SetName(Keys.CFM, Enum.GetName(cryptFilterMethod) ?? throw TH.InvalidOperationException_InvalidCryptFilterMethod());
+#if NET8_0_OR_GREATER
+ Elements.SetName(Keys.CFM, '/' + Enum.GetName(cryptFilterMethod) ?? throw TH.InvalidOperationException_InvalidCryptFilterMethod());
#else
- Elements.SetName(Keys.CFM, Enum.GetName(typeof(CryptFilterMethod), cryptFilterMethod) ?? throw TH.InvalidOperationException_InvalidCryptFilterMethod());
+ Elements.SetName(Keys.CFM, '/' + Enum.GetName(typeof(CryptFilterMethod), cryptFilterMethod) ?? throw TH.InvalidOperationException_InvalidCryptFilterMethod());
#endif
}
CryptFilterMethod GetCryptFilterMethod()
{
-#if NET6_0_OR_GREATER
- _cryptFilterMethod ??= Enum.Parse(PdfName.RemoveSlash(Elements.GetName(Keys.CFM)));
+#if NET8_0_OR_GREATER
+ _cryptFilterMethod ??= Enum.Parse(Name.RemoveSlash(Elements.GetName(Keys.CFM)));
#else
- _cryptFilterMethod ??= (CryptFilterMethod?)Enum.Parse(typeof(CryptFilterMethod), PdfName.RemoveSlash(Elements.GetName(Keys.CFM)));
+ _cryptFilterMethod ??= (CryptFilterMethod?)Enum.Parse(typeof(CryptFilterMethod), Name.RemoveSlash(Elements.GetName(Keys.CFM)));
#endif
return _cryptFilterMethod ?? CryptFilterMethod.None;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilters.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilters.cs
index 3ef3cd99..5cab5ea4 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilters.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilters.cs
@@ -17,9 +17,9 @@ public PdfCryptFilters()
{ }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
- ///
internal PdfCryptFilters(PdfDictionary dict) : base(dict)
{ }
@@ -28,8 +28,9 @@ internal PdfCryptFilters(PdfDictionary dict) : base(dict)
///
public PdfCryptFilter? GetCryptFilter(string name)
{
- var key = PdfName.AddSlash(name);
- var value = Elements[key];
+ var key = Name.MakeName(name);
+ //var value = Elements[key]; // #US373: Should we expect references here?
+ var value = Elements.GetValue(key); // #US373: Should we expect references here?
return value is null ? null : Convert(value, key);
}
@@ -39,7 +40,7 @@ internal PdfCryptFilters(PdfDictionary dict) : base(dict)
///
public void AddCryptFilter(string name, PdfCryptFilter cryptFilter)
{
- var key = PdfName.AddSlash(name);
+ var key = Name.MakeName(name);
Elements[key] = cryptFilter;
}
@@ -48,7 +49,7 @@ public void AddCryptFilter(string name, PdfCryptFilter cryptFilter)
///
public bool RemoveCryptFilter(string name)
{
- var key = PdfName.AddSlash(name);
+ var key = Name.MakeName(name);
return Elements.Remove(key);
}
@@ -62,8 +63,9 @@ public bool RemoveCryptFilter(string name)
// Instead, enumerate Keys and get value via Elements[key], which shall be O(1).
foreach (var key in Elements.Keys)
{
- var value = Elements[key]!;
- var name = PdfName.RemoveSlash(key);
+ //var value = Elements[key]!; // #US373: Should we expect references here?
+ var value = Elements.GetValue(key)!; // #US373: Should we expect references here?
+ var name = Name.RemoveSlash(key);
var cryptFilter = Convert(value, key);
yield return (name, cryptFilter);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecurityHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecurityHandler.cs
index 25e43cae..24e25116 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecurityHandler.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecurityHandler.cs
@@ -14,6 +14,10 @@ internal PdfSecurityHandler(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfSecurityHandler(PdfDictionary dict)
: base(dict)
{ }
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecuritySettings.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecuritySettings.cs
index 00685c68..6aa7d57f 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecuritySettings.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecuritySettings.cs
@@ -1,6 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal;
+
namespace PdfSharp.Pdf.Security
{
///
@@ -16,17 +18,10 @@ internal PdfSecuritySettings(PdfDocument document)
readonly PdfDocument _document;
///
- /// Indicates whether the granted access to the document is 'owner permission'. Returns true if the document
- /// is unprotected or was opened with the owner password. Returns false if the document was opened with the
- /// user password.
+ /// Indicates whether the document is opened with full permission.
+ /// Returns true unless an owner password secured document was opened with the user password.
///
- public bool HasOwnerPermissions
- {
- internal get => _hasOwnerPermissions;
- set => _hasOwnerPermissions = value;
- }
- /*internal*/
- bool _hasOwnerPermissions = true;
+ public bool HasFullPermission { get; internal set; } = true;
///
/// Sets the user password of the document. Setting a password automatically sets the
@@ -62,6 +57,9 @@ internal bool CanSave(ref string message)
if (effectiveSecurityHandler != null)
{
+ if (effectiveSecurityHandler.DoNotResetEncryption)
+ return true;
+
if (String.IsNullOrEmpty(effectiveSecurityHandler.UserPassword) && String.IsNullOrEmpty(effectiveSecurityHandler.OwnerPassword))
{
message = PsMsgs.UserOrOwnerPasswordRequired;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfStandardSecurityHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfStandardSecurityHandler.cs
index 47b17375..dbfa98b3 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfStandardSecurityHandler.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfStandardSecurityHandler.cs
@@ -5,6 +5,7 @@
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.IO;
using PdfSharp.Pdf.Security.Encryption;
+using PdfSharp.Pdf.Signatures;
namespace PdfSharp.Pdf.Security
{
@@ -16,6 +17,10 @@ public sealed class PdfStandardSecurityHandler : PdfSecurityHandler
internal PdfStandardSecurityHandler(PdfDocument document) : base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfStandardSecurityHandler(PdfDictionary dict) : base(dict)
{ }
@@ -33,7 +38,6 @@ void SetDefaultEncryption()
///
/// Do not encrypt the PDF file. Resets the user and owner password.
///
-
public void SetEncryptionToNoneAndResetPasswords()
{
_userPassword = "";
@@ -59,24 +63,31 @@ public void SetEncryption(PdfDefaultEncryption encryption)
case PdfDefaultEncryption.None:
SetEncryptionToNoneAndResetPasswords();
break;
+
case PdfDefaultEncryption.Default:
SetDefaultEncryption();
break;
+
case PdfDefaultEncryption.V1:
SetEncryptionToV1();
break;
+
case PdfDefaultEncryption.V2With40Bits:
SetEncryptionToV2();
break;
+
case PdfDefaultEncryption.V2With128Bits:
SetEncryptionToV2With128Bits();
break;
+
case PdfDefaultEncryption.V4UsingRC4:
SetEncryptionToV4UsingRC4();
break;
+
case PdfDefaultEncryption.V4UsingAES:
SetEncryptionToV4UsingAES();
break;
+
case PdfDefaultEncryption.V5:
SetEncryptionToV5();
break;
@@ -98,6 +109,10 @@ public void SetEncryptionToV1()
/// The file encryption key length - a multiple of 8 from 40 to 128 bit.
public void SetEncryptionToV2(int length = 40)
{
+ // There is a specific problem with Microsoft Edge and encryption version 2.
+ // If the PDF is saved with an owner AND a user password with version 2 and 40 bit key length, the new PDF viewer of Edge 143.0.3650.96 is not
+ // able to open the file with the owner password. For a key length of 48 bit it’s the same.
+ // All other encryption versions work correctly in Edge, so use a newer encryption version or SetEncryptionToV2With128Bits() if version 2 is required.
SetEncryptionFieldToV1To4().SetEncryptionToV2(length);
}
@@ -160,7 +175,12 @@ PdfEncryptionV5 SetEncryptionFieldToV5()
///
internal PdfStandardSecurityHandler? GetIfEncryptionIsActive() => IsEncrypted ? this : null;
- bool IsEncrypted => _encryption != null;
+ bool IsEncrypted => _encryption != null;
+
+ ///
+ /// Indicates that the encryption of an opened document shall not be changed.
+ ///
+ public bool DoNotResetEncryption { get; set; }
///
/// Sets the user password of the document.
@@ -189,7 +209,7 @@ public string OwnerPassword
}
}
- private string _ownerPassword = "";
+ string _ownerPassword = "";
///
/// Gets or sets the user access permission represented as an unsigned 32-bit integer in the P key.
@@ -215,7 +235,7 @@ internal uint GetCorrectedPermissionsValue()
// Correct permission bits.
permissionsValue &= 0xfffffffc; // 1... 1111 1111 1100 - Bit 1 & 2 must be 0.
-#if true
+#if true // TODO clean up
//permissionsValue |= 0x000002c0; // 0... 0010 1100 0000 - Bit 7 & 8 must be 1. Also, Bit 10 is no longer used and shall be always set to 1.
// Top-most bit not correct, but can also be read with PDFsharp up to 6.1.0.
permissionsValue |= 0x7ffff2c0; // 01.. 1110 1100 0000 - Bit 7 & 8 & 13 through 32 must be 1. Also, Bit 10 is no longer used and shall be always set to 1.
@@ -223,7 +243,6 @@ internal uint GetCorrectedPermissionsValue()
// Include this later as files can not be read with PDFsharp up to 6.1.0.
permissionsValue |= 0xfffff2c0; // 1... 1110 1100 0000 - Bit 7 & 8 & 13 through 32 must be 1. Also, Bit 10 is no longer used and shall be always set to 1.
#endif
-
return permissionsValue;
}
@@ -252,7 +271,7 @@ bool IsSecurityHandler(PdfObject pdfObject)
return false;
// Incrementally updated PDFs contain multiple trailers. Check the SecurityHandler of each one.
- var currentTrailer = _document.Trailer;
+ var currentTrailer = Document.Trailer;
while (currentTrailer != null)
{
// Compare PdfReference, as currentTrailer.SecurityHandler contains the dictionary converted to PdfStandardSecurityHandler,
@@ -288,9 +307,11 @@ public void DecryptObject(PdfObject value)
case PdfDictionary vDict:
DecryptDictionary(vDict);
break;
+
case PdfArray vArray:
DecryptArray(vArray);
break;
+
case PdfStringObject vStr:
DecryptString(vStr);
break;
@@ -304,16 +325,20 @@ public void DecryptObject(PdfObject value)
///
void DecryptDictionary(PdfDictionary dict)
{
- foreach (var item in dict.Elements)
+ var elementsToDecrypt = GetElementsToDecrypt(dict);
+
+ foreach (var item in elementsToDecrypt)
{
switch (item.Value)
{
case PdfString vStr:
DecryptString(vStr);
break;
+
case PdfDictionary vDict:
DecryptDictionary(vDict);
break;
+
case PdfArray vArray:
DecryptArray(vArray);
break;
@@ -330,6 +355,33 @@ void DecryptDictionary(PdfDictionary dict)
}
}
+ IEnumerable> GetElementsToDecrypt(PdfDictionary dict)
+ {
+ var elements = dict.Elements;
+
+ // According to PDF Reference 2.0, hexadecimal strings in the /Contents key of a signature dictionary shall not be encrypted (and so shall not decrypted).
+
+ // However, it’s hard to identify a signature dictionary, as the /Type key is optional. You could identify it by being the dictionary referenced via /V key
+ // of a signature field, which itself could be identified by its /FT key set to /Sig. But as decryption is done while reading all indirect objects, the
+ // relationship between the signature field and the signature dictionary isn’t modelled yet.
+ // But due to the following specifications of PDF Reference 2.0, /Contents key must only be excluded from decryption, if /ByteRange key is present, which is
+ // a unique key, we can use for identification:
+ // - 7.6.2 Application of encryption: "Encryption applies to all strings and streams in the document's PDF file, with the following exceptions: [...]
+ // Any hexadecimal strings representing the value of the Contents key in a Signature dictionary"
+ // - Table 255 — Entries in a signature dictionary:
+ // - "ByteRange - array - [...] When a byte range digest is present, all values in the signature dictionary shall be direct objects.
+ // - "Contents - byte string - (Required) The signature value. When ByteRange is present, the value shall be a hexadecimal string (see 7.3.4.3, "Hexadecimal strings")
+ // representing the value of the byte range digest."
+
+ // So checking for /ByteRange and /Contents keys is sufficient to identify the constellation, where /Contents key must not be decrypted.
+ var isSignatureDictWithHexByteRange = elements.ContainsKey(PdfSignature.Keys.ByteRange) && elements.ContainsKey(PdfSignature.Keys.Contents);
+ if (isSignatureDictWithHexByteRange)
+ return elements.Where(x => x.Key != PdfSignature.Keys.Contents);
+
+ // For all other cases, all elements of the dictionary shall be processed.
+ return elements;
+ }
+
///
/// Decrypts an array.
///
@@ -345,9 +397,11 @@ void DecryptArray(PdfArray array)
case PdfString vStr:
DecryptString(vStr);
break;
+
case PdfDictionary vDict:
DecryptDictionary(vDict);
break;
+
case PdfArray vArray:
DecryptArray(vArray);
break;
@@ -487,6 +541,9 @@ internal void PrepareForReading()
SetEncryptionFieldToV1To4();
else if (PdfEncryptionV5.IsVersionSupported(versionValue))
SetEncryptionFieldToV5();
+ else
+ throw TH.PdfReaderException_UnknownEncryption();
+
GetEncryption().InitializeFromLoadedSecurityHandler();
// Load, initialize and prepare crypt filters.
@@ -503,6 +560,10 @@ internal void PrepareForReading()
///
internal void PrepareForWriting()
{
+ // Return if encryption shall be left unchanged.
+ if (DoNotResetEncryption)
+ return;
+
Elements[PdfSecurityHandler.Keys.Filter] = new PdfName("/Standard");
GetEncryption().PrepareEncryptionForSaving(UserPassword, OwnerPassword);
@@ -525,7 +586,7 @@ internal PasswordValidity ValidatePassword(string? inputPassword)
inputPassword ??= "";
var passwordValidity = GetEncryption().ValidatePassword(inputPassword);
- _document.SecuritySettings.HasOwnerPermissions = passwordValidity == PasswordValidity.OwnerPassword;
+ Document.SecuritySettings.HasFullPermission = passwordValidity == PasswordValidity.OwnerPassword;
return passwordValidity;
}
@@ -726,7 +787,7 @@ void SetCryptFilterAsDefaultInternal(string key, string? name)
return;
}
- if (name != PdfName.RemoveSlash(CryptFilterConstants.IdentityFilterValue))
+ if (name != Name.RemoveSlash(CryptFilterConstants.IdentityFilterValue))
{
var pdfCryptFilters = (PdfCryptFilters?)Elements.GetValue(PdfSecurityHandler.Keys.CF);
if (pdfCryptFilters?.GetCryptFilter(name) is null)
@@ -756,9 +817,9 @@ void LoadCryptFilters(bool initializeCryptFilters)
loadedCryptFilter.Value.Initialize(this);
}
- _defaultCryptFilterStreams = GetDefaultCryptFilter(PdfName.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.StmF)));
- _defaultCryptFilterStrings = GetDefaultCryptFilter(PdfName.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.StrF)));
- _defaultCryptFilterEmbeddedFileStreams = GetDefaultCryptFilter(PdfName.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.EFF)), _defaultCryptFilterStreams);
+ _defaultCryptFilterStreams = GetDefaultCryptFilter(Name.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.StmF)));
+ _defaultCryptFilterStrings = GetDefaultCryptFilter(Name.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.StrF)));
+ _defaultCryptFilterEmbeddedFileStreams = GetDefaultCryptFilter(Name.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.EFF)), _defaultCryptFilterStreams);
}
CryptFilterBase GetDefaultCryptFilter(string cryptFilterName)
@@ -768,7 +829,7 @@ CryptFilterBase GetDefaultCryptFilter(string cryptFilterName)
CryptFilterBase GetDefaultCryptFilter(string cryptFilterName, CryptFilterBase @default)
{
- if (cryptFilterName == PdfName.RemoveSlash(CryptFilterConstants.IdentityFilterValue))
+ if (cryptFilterName == Name.RemoveSlash(CryptFilterConstants.IdentityFilterValue))
return IdentityCryptFilter.Instance;
if (string.IsNullOrEmpty(cryptFilterName))
@@ -793,7 +854,7 @@ public void ResetCryptFilter(PdfDictionary dictionary)
void ResetCryptFilterEntriesInAllElements()
{
- foreach (var iref in _document.IrefTable.AllReferences)
+ foreach (var iref in Document.IrefTable.AllReferences)
{
var pdfObject = iref.Value;
if (pdfObject is not PdfDictionary dictionary)
@@ -820,7 +881,7 @@ public void SetCryptFilter(PdfDictionary dictionary, string cryptFilterName)
EnsureCryptFiltersAreSupported();
- if (PdfName.AddSlash(cryptFilterName) != CryptFilterConstants.IdentityFilterValue)
+ if (Name.MakeName(cryptFilterName) != CryptFilterConstants.IdentityFilterValue)
{
var cryptFilters = (PdfCryptFilters?)Elements.GetValue(PdfSecurityHandler.Keys.CF);
if (cryptFilters?.GetCryptFilter(cryptFilterName) is null)
@@ -829,7 +890,7 @@ public void SetCryptFilter(PdfDictionary dictionary, string cryptFilterName)
dictionary.Elements.ArrayOrSingleItem.Add(PdfStream.Keys.Filter, new PdfName(CryptFilterConstants.FilterValue), true);
- var decodeParams = new PdfDictionary(dictionary._document);
+ var decodeParams = new PdfDictionary(dictionary.Document);
decodeParams.Elements.SetName(CryptFilterConstants.DecodeParmsTypeKey, CryptFilterConstants.DecodeParmsTypeValue);
decodeParams.Elements.SetName(CryptFilterConstants.DecodeParmsNameKey, cryptFilterName);
@@ -869,7 +930,7 @@ public void SetCryptFilter(PdfDictionary dictionary, string cryptFilterName)
return IdentityCryptFilter.Instance;
// For others try to load crypt filter form _loadedCryptFilters.
- var cryptFilterName = PdfName.RemoveSlash(cryptFilterNameValue);
+ var cryptFilterName = Name.RemoveSlash(cryptFilterNameValue);
if (!string.IsNullOrEmpty(cryptFilterName))
return _loadedCryptFilters![cryptFilterName];
}
@@ -882,6 +943,18 @@ public void SetCryptFilter(PdfDictionary dictionary, string cryptFilterName)
throw TH.InvalidOperationException_CryptFilterDecodeParmsNotInitializedCorrectly();
}
+ // In general the identity crypt filter should be added to not encrypted dictionaries.
+ // But some PDF tools don’t add it to the metadata dictionary if EncryptMetadata is set to false.
+ // So if we didn’t find a crypt filter, we check if a metadata dictionary is not encrypted and
+ // return the identity crypt filter in that case to avoid decrypting it.
+ type = dictionary.Elements.GetName(PdfMetadata.Keys.Type);
+ if (type == "/Metadata" && !GetEncryption().EncryptMetadata)
+ {
+ var possiblyReadableXml = dictionary.Stream?.ToString().TrimStart();
+ if (possiblyReadableXml != null && possiblyReadableXml[0] == '<')
+ return IdentityCryptFilter.Instance;
+ }
+
if (PdfEmbeddedFileStream.IsEmbeddedFile(dictionary))
return _defaultCryptFilterEmbeddedFileStreams;
@@ -1047,7 +1120,7 @@ static class CryptFilterConstants
///
/// Gets the KeysMeta for these keys.
///
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/SecurityManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/SecurityManager.cs
new file mode 100644
index 00000000..009f3ced
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/SecurityManager.cs
@@ -0,0 +1,81 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.Security
+{
+ using Internal;
+
+ public class SecurityManager : ManagerBase
+ {
+ SecurityManager(PdfDocument document) : base(document)
+ {
+ Initialize();
+ }
+
+ public PdfSecuritySettings SecuritySettings
+ => Document.SecuritySettings;
+
+ public string EncryptionType { get; } = "(TODO)"; // TODO
+
+ public string? UserPassword // TODO
+ {
+ get => _userPassword;
+ set
+ {
+ if (value == null)
+ {
+ _userPassword = null;
+ }
+ else if (_userPassword is null)
+ {
+ _userPassword = value;
+ }
+ else
+ throw new InvalidOperationException("Cannot change user password if once set.");
+ }
+ }
+ string? _userPassword = "(TODO)"; // TODO
+
+ public string? OwnerPassword // TODO
+ {
+ get => _ownerPassword;
+ set
+ {
+ var i = base.IsInitialized;
+ if (value == null)
+ {
+ _ownerPassword = null;
+ }
+ else if (_ownerPassword is null)
+ {
+ _ownerPassword = value;
+ }
+ else
+ throw new InvalidOperationException("Cannot change owner password if once set.");
+ }
+ }
+ string? _ownerPassword = "(TODO)"; // TODO
+
+ ///
+ /// Gets or creates the SecurityManager singleton for the specified document.
+ ///
+ public static SecurityManager ForDocument(PdfDocument document)
+ => document.SecurityManager ??= new(document);
+
+ void Initialize()
+ {
+ Document.EnsureNotDisposed();
+ if (!IsInitialized)
+ {
+ IsInitialized = true;
+ Document.EnsureNotYetSaved();
+ if (Document.IsImported)
+ {
+ // TODO: Get PDF/A conformance
+ }
+ }
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/enums/PdfUserAccessPermission.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/enums/PdfUserAccessPermission.cs
index 52e27df5..9c3f4324 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/enums/PdfUserAccessPermission.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/enums/PdfUserAccessPermission.cs
@@ -9,9 +9,6 @@ namespace PdfSharp.Pdf.Security
[Flags]
enum PdfUserAccessPermission : uint
{
- // PDF 2.0: 7.6.4.2 Standard encryption dictionary
- // See table 22.
-
///
/// Permits everything. This is the default value.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DefaultSignatureAppearanceHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DefaultSignatureAppearanceHandler.cs
index 7dd6f656..dd57066e 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DefaultSignatureAppearanceHandler.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DefaultSignatureAppearanceHandler.cs
@@ -5,6 +5,8 @@
using PdfSharp.Drawing.Layout;
using PdfSharp.Pdf.Annotations;
+// v7.0.0 TODO review
+
namespace PdfSharp.Pdf.Signatures
{
///
@@ -21,7 +23,7 @@ class DefaultSignatureAppearanceHandler : IAnnotationAppearanceHandler
public void DrawAppearance(XGraphics gfx, XRect rect)
{
- var defaultText = $"Signed by: {Signer}\nLocation: {Location}\nReason: {Reason}\nDate: {DateTime.Now}";
+ var defaultText = $"Signed by: {Signer}\nLocation: {Location}\nReason: {Reason}\nDate: {DateTimeOffset.Now}";
// You should write your own implementation of IAnnotationAppearanceHandler and ensure that the used font is available.
var font = new XFont("Verdana", 7, XFontStyleEx.Regular);
@@ -34,8 +36,8 @@ public void DrawAppearance(XGraphics gfx, XRect rect)
// Leave 5% space on each side.
txtFormat.DrawString(defaultText, font,
- new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)),
- new XRect(currentPosition.X + width * .05, currentPosition.Y + height * .05,
+ XBrushes.Black,
+ new XRect(currentPosition.X + width * .05, currentPosition.Y + height * .05,
width * .9, height * .9),
XStringFormats.TopLeft);
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureHandler.cs
new file mode 100644
index 00000000..a6d00f3c
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureHandler.cs
@@ -0,0 +1,269 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+#if !NET8_0_OR_GREATER
+using System.Text;
+#endif
+using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Annotations;
+using PdfSharp.Pdf.Forms;
+using PdfSharp.Pdf.IO;
+using System.Text;
+
+// v7.0.0 TODO review
+
+namespace PdfSharp.Pdf.Signatures
+{
+ ///
+ /// PdfDocument signature handler.
+ /// Attaches a PKCS#7 signature digest to PdfDocument.
+ ///
+ public class DigitalSignatureHandler
+ {
+ ///
+ /// Large enough space reserved by PdfPlaceholder to be replaced by the actual computed value of the byte range to sign.
+ /// Worst case: signature dictionary is near the end of an 10 GB PDF file. So we reserve 10 digits.
+ /// However, the current implementation can only support 2 GB files.
+ ///
+ const int ByteRangePlaceholderLength = 36; // = "[0 9999999999 9999999999 9999999999]".Length
+
+ DigitalSignatureHandler(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options)
+ {
+ Document = document ?? throw new ArgumentNullException(nameof(document));
+ Signer = signer ?? throw new ArgumentNullException(nameof(signer));
+ Options = options ?? throw new ArgumentNullException(nameof(options));
+
+ if (options.PageIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(options.PageIndex),
+ "Signature page index cannot be negative.");
+ }
+
+ // TODO_OLD in document: Set document version depending on digest type from options. // TODO: what?
+ }
+
+ ///
+ /// Gets or creates the digital signature handler for the specified document.
+ ///
+ public static DigitalSignatureHandler ForDocument(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options)
+ {
+ return document.DigitalSignatureHandler ??= new(document, signer, options);
+ }
+
+ ///
+ /// Gets the PDF document the signature will be attached to.
+ ///
+ public PdfDocument Document { get; init; }
+
+ ///
+ /// Gets the options for the digital signature.
+ ///
+ public DigitalSignatureOptions Options { get; init; }
+
+ IDigitalSigner Signer { get; init; }
+
+ ///
+ /// Computes the signature and byte range after the document stream was written.
+ ///
+ internal async Task ComputeSignatureAndRange(PdfWriter writer)
+ {
+ var (rangedStreamToSign, byteRangeArray) = GetRangeToSignAndByteRangeArray(writer.Stream);
+
+ // Write the /ByteRange entry '[...2 times offset, length...]'.
+ Debug.Assert(_byteRangePlaceholder != null);
+ var sb = new StringBuilder(4096);
+ sb.Append(byteRangeArray);
+ // Length must match placeholder.
+ sb.Append(new String(' ', _byteRangePlaceholder.Length - sb.Length));
+ _byteRangePlaceholder.SetValue(sb.ToString());
+ _byteRangePlaceholder.WriteEffectiveValue(writer);
+
+ // Computing signature from document’s digest.
+ var signature = await Signer.GetSignatureAsync(rangedStreamToSign).ConfigureAwait(false);
+
+ Debug.Assert(_contentsPlaceholder != null);
+ //int expectedLength = (_contentsPlaceholder.Length - 2) / 2;
+ int contentsHexLength = 2 * signature.Length + 2;
+ if (contentsHexLength > _contentsPlaceholder.Length)
+ {
+ // This should not happen.
+ throw new InvalidOperationException(
+ $"The actual digest length '{contentsHexLength}' is larger than the approximation made '{_contentsPlaceholder.Length}'. " +
+ "Not enough space in the placeholder to fit the hex-encoded signature.");
+ }
+
+ // Write the /Contents entry '<...signature hex string...>'.
+ // When the signature includes a timestamp, the exact length is unknown until the signature is definitely calculated.
+ // Therefore, we write the angle brackets here and override the placeholder white-spaces.
+ // According to the PDF reference, the Contents key of a signature dictionary shall not be encrypted.
+ sb.Clear();
+ sb.Append('<');
+ sb.Append(FormatHex(signature));
+#if true
+ // Filler bytes must be part of the hex string. Trailing blanks are invalid PDF.
+ //sb.Append(new String('0', 2 * (expectedLength - signature.Length)));
+ sb.Append(new String('0', _contentsPlaceholder.Length - sb.Length - 1));
+ sb.Append('>');
+#else
+ sb.Append('>');
+ sb.Append(new String(' ', 2 * (expectedLength - signature.Length)));
+#endif
+ _contentsPlaceholder.SetValue(sb.ToString());
+ _contentsPlaceholder.WriteEffectiveValue(writer);
+ }
+
+ string FormatHex(byte[] bytes) // ...use RawEncoder
+ {
+#if NET8_0_OR_GREATER
+ return Convert.ToHexString(bytes);
+#else
+ var result = new StringBuilder();
+
+ for (int idx = 0; idx < bytes.Length; idx++)
+ result.AppendFormat("{0:X2}", bytes[idx]);
+
+ return result.ToString();
+#endif
+ }
+
+ ///
+ /// Get the bytes ranges to sign.
+ /// As recommended in PDF specs, whole document will be signed, except for the hexadecimal signature token value in the /Contents entry.
+ /// Example: '/Contents <aaaaa111111>' => '<aaaaa111111>' will be excluded from the bytes to sign.
+ ///
+ ///
+ (RangedStream rangedStream, PdfArray byteRangeArray) GetRangeToSignAndByteRangeArray(Stream stream)
+ {
+ Debug.Assert(_contentsPlaceholder != null, nameof(_contentsPlaceholder) + " must not be null here.");
+
+ var firstRangeOffset = 0L;
+ var firstRangeLength = _contentsPlaceholder.StartPosition;
+ var secondRangeOffset = _contentsPlaceholder.EndPosition;
+ var secondRangeLength = stream.Length - _contentsPlaceholder.EndPosition;
+
+ var byteRangeArray = new PdfArray();
+#if USE_LONG_SIZE
+ byteRangeArray.Elements.Add(new PdfLongInteger(firstRangeOffset));
+ byteRangeArray.Elements.Add(new PdfLongInteger(firstRangeLength));
+ byteRangeArray.Elements.Add(new PdfLongInteger(secondRangeOffset));
+ byteRangeArray.Elements.Add(new PdfLongInteger(secondRangeLength));
+#else
+ byteRangeArray.Elements.Add(new PdfInteger(firstRangeOffset));
+ byteRangeArray.Elements.Add(new PdfInteger(firstRangeLength));
+ byteRangeArray.Elements.Add(new PdfInteger(secondRangeOffset));
+ byteRangeArray.Elements.Add(new PdfInteger(secondRangeLength));
+#endif
+ var rangedStream = new RangedStream(stream,
+ [
+ new(firstRangeOffset, firstRangeLength),
+ new(secondRangeOffset, secondRangeLength)
+ ]);
+
+ return (rangedStream, byteRangeArray);
+ }
+
+ ///
+ /// Adds the PDF objects required for a digital signature as placeholders.
+ ///
+ ///
+ internal async Task AddSignatureComponentsAsync() // #US321 TODO Use appropriate classes.
+ {
+ if (Options.PageIndex >= Document.PageCount)
+ throw new ArgumentOutOfRangeException($"Signature page doesn't exist, specified page was {Options.PageIndex + 1} but document has only {Document.PageCount} page(s).");
+
+ var signatureSize = await Signer.GetSignatureSizeAsync().ConfigureAwait(false);
+ _contentsPlaceholder = new(2 * signatureSize + 2);
+ _byteRangePlaceholder = new(ByteRangePlaceholderLength);
+
+ var signatureDictionary = GetSignatureDictionary(_contentsPlaceholder, _byteRangePlaceholder);
+ var signatureField = GetSignatureField(signatureDictionary);
+
+ var page = Document.Pages[Options.PageIndex];
+ var annotations = page.Elements.GetArray(PdfPage.Keys.Annots);
+ if (annotations == null)
+ page.Elements.Add(PdfPage.Keys.Annots, new PdfArray(Document, signatureField));
+ else
+ annotations.Elements.Add(signatureField);
+
+ var catalog = Document.Catalog;
+ var acroForm = catalog.GetOrCreateAcroForm();
+
+ if (!acroForm.Elements.ContainsKey(PdfForm.Keys.SigFlags))
+ acroForm.Elements.Add(PdfForm.Keys.SigFlags, new PdfInteger(3, true));
+ else
+ {
+ var sigFlagVersion = acroForm.Elements.GetInteger(PdfForm.Keys.SigFlags);
+ if (sigFlagVersion < 3)
+ acroForm.Elements.SetIntegerFlag(PdfForm.Keys.SigFlags, 3);
+ }
+
+ acroForm.Fields.Elements.Add(signatureField);
+ }
+
+ PdfFormSignatureField GetSignatureField(PdfSignature signatureDic) // #US321 TODO Use appropriate classes.
+ {
+ var signatureField = new PdfFormSignatureField(Document);
+
+ signatureField.Elements.Add(PdfFormField.Keys.V, signatureDic);
+
+ // #AcroForms
+ // Annotation keys.
+ signatureField.Elements.Add(PdfFormField.Keys.FT, new PdfName(PdfFormFieldType.Signature));
+ signatureField.Elements.Add(PdfFormField.Keys.T, new PdfString("Signature1")); // TODO If already exists, will it cause error? implement a name chooser if yes.
+ signatureField.Elements.Add(PdfFormField.Keys.Ff, new PdfInteger(132));
+ // signatureField.Elements.Add(PdfFormField.Keys.DR, new PdfDictionary()); TODO COMPILE
+ signatureField.Elements.Add(PdfAnnotation.Keys.Type, new PdfName("/Annot"));
+ signatureField.Elements.Add(PdfAnnotation.Keys.Subtype, new PdfName("/Widget"));
+ signatureField.Elements.Add(PdfAnnotation.Keys.P, Document.Pages[Options.PageIndex]);
+
+ signatureField.Elements.Add(PdfAnnotation.Keys.Rect, new PdfRectangle(Options.Rectangle));
+
+ // TODO COMPILE
+ signatureField.CustomAppearanceHandler = Options.AppearanceHandler ?? new DefaultSignatureAppearanceHandler()
+ {
+ Location = Options.Location,
+ Reason = Options.Reason,
+ Signer = Signer.CertificateName
+ };
+
+ // Call RenderCustomAppearance(); here.
+ signatureField.RenderAppearance();
+ // Rendering the signature in PrepareForSave is too late and leads to inconsistent embedded fonts.
+
+ //Document.Internals.AddObject(signatureField); AcroFields are already indirect.
+
+ return signatureField;
+ }
+
+ PdfSignature GetSignatureDictionary(PdfPlaceholder contents, PdfPlaceholder byteRange) // #US321 TODO Use appropriate classes.
+ {
+ PdfSignature signatureDic = new(Document);
+
+ signatureDic.Elements.Add(PdfSignature.Keys.Type, new PdfName(PdfFormFieldType.Signature));
+ signatureDic.Elements.Add(PdfSignature.Keys.Filter, new PdfName("/Adobe.PPKLite"));
+ signatureDic.Elements.Add(PdfSignature.Keys.SubFilter, new PdfName("/adbe.pkcs7.detached"));
+ signatureDic.Elements.Add(PdfSignature.Keys.M, new PdfDate(DateTimeOffset.Now));
+
+ signatureDic.Elements.Add(PdfSignature.Keys.Contents, contents);
+ signatureDic.Elements.Add(PdfSignature.Keys.ByteRange, byteRange);
+ signatureDic.Elements.Add(PdfSignature.Keys.Reason, new PdfString(Options.Reason));
+ signatureDic.Elements.Add(PdfSignature.Keys.Location, new PdfString(Options.Location));
+
+ var properties = new PdfDictionary(Document);
+ signatureDic.Elements.Add("/Prop_Build", properties);
+ var propertyItems = new PdfDictionary(Document);
+ properties.Elements.Add("/App", propertyItems);
+ propertyItems.Elements.Add("/Name",
+ String.IsNullOrWhiteSpace(Options.AppName) ?
+ new PdfName("/PDFsharp http://www.pdfsharp.net") :
+ PdfName.FromString(Options.AppName));
+
+ Document.Internals.AddObject(signatureDic);
+
+ return signatureDic;
+ }
+
+ PdfPlaceholder? _contentsPlaceholder;
+ PdfPlaceholder? _byteRangePlaceholder;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureOptions.cs
index c51bc051..ed4aacdc 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureOptions.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureOptions.cs
@@ -4,6 +4,8 @@
using PdfSharp.Drawing;
using PdfSharp.Pdf.Annotations;
+// v7.0.0 Ready
+
namespace PdfSharp.Pdf.Signatures
{
///
@@ -34,7 +36,7 @@ public class DigitalSignatureOptions()
///
/// Gets or sets the name of the application used to sign the document.
///
- public string AppName { get; init; } = "PDFsharp http://www.pdfsharp.net";
+ public string AppName { get; init; } = "PDFsharp http://www.pdfsharp.com";
///
/// The location of the visual representation on the selected page.
@@ -42,7 +44,7 @@ public class DigitalSignatureOptions()
public XRect Rectangle { get; init; }
///
- /// The page index, zero-based, of the page showing the signature.
+ /// The page zero-based index of the page showing the signature.
///
public int PageIndex { get; init; }
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfArrayWithPadding.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfArrayWithPadding.cs
deleted file mode 100644
index 8c4af860..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfArrayWithPadding.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using PdfSharp.Pdf.IO;
-
-namespace PdfSharp.Pdf.Signatures
-{
- ///
- /// Internal PDF array used for digital signatures.
- /// For digital signatures, we have to add an array with four integers,
- /// but at the time we add the array we cannot yet determine
- /// how many digits those integers will have.
- ///
- /// The document.
- /// The count of spaces added after the array.
- /// The contents of the array.
- sealed class PdfArrayWithPadding(PdfDocument document, int paddingRight, params PdfItem[] items)
- : PdfArray(document, items)
- {
- public int PaddingRight { get; init; } = paddingRight;
-
- internal override void WriteObject(PdfWriter writer)
- {
- StartPosition = writer.Position;
-
- base.WriteObject(writer);
- writer.WriteRaw(new String(' ', PaddingRight));
- }
-
- ///
- /// Position of the first byte of this string in PdfWriter’s stream.
- ///
- public SizeType StartPosition { get; internal set; }
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs
new file mode 100644
index 00000000..8a58effb
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs
@@ -0,0 +1,64 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.IO;
+
+namespace PdfSharp.Pdf.Signatures
+{
+ ///
+ /// This object reserves space in the stream of the PdfWriter by adding spaces for objects that can not be written at this time.
+ /// It is used e.g. for digital signatures to reserve space for the ByteRange Array.
+ ///
+ /// The length of the reserved space.
+ sealed class PdfPlaceholderObject_(int length) : PdfObject // TODO: use PdfPlaceholder DELETE
+ {
+ public int Length { get; init; } = length;
+
+ ///
+ /// This only writes spaces as placeholder for the actual object.
+ ///
+ internal override void WriteObject(PdfWriter writer)
+ {
+ StartPosition = writer.Position;
+
+ writer.WriteRaw(new String(' ', Length));
+ }
+
+ ///
+ /// Writes the object, the placeholder is used for.
+ ///
+ /// The object to write.
+ /// The PDFWriter.
+ /// Throws an exception, when the space reserved by the placeholder isn’t enough.
+ internal void WriteActualObject(PdfObject obj, PdfWriter writer)
+ {
+ Object = obj;
+
+ // Cache current writer position.
+ var initialPosition = writer.Position;
+
+ // Write Object at StartPosition.
+ writer.Stream.Position = StartPosition;
+ Object.WriteObject(writer);
+
+ // Ensure that object doesn’t exceed the reserved space.
+ var endPosition = writer.Position;
+ var actualLength = endPosition - StartPosition;
+ if (actualLength > Length)
+ throw new InvalidOperationException($"The actual length {actualLength} of this object is larger than the length {Length} of its placeholder.");
+
+ // Restore writer position.
+ writer.Stream.Position = initialPosition;
+ }
+
+ ///
+ /// Position of this object in PdfWriter’s stream.
+ ///
+ public SizeType StartPosition { get; internal set; }
+
+ ///
+ /// The object replacing the reserved space.
+ ///
+ public PdfObject? Object { get; private set; }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignature.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignature.cs
index 364b50ca..48d1547a 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignature.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignature.cs
@@ -1,23 +1,32 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+// v7.0.0 TODO review
+
namespace PdfSharp.Pdf.Signatures
{
///
/// The signature dictionary added to a PDF file when it is to be signed.
///
- public sealed class PdfSignature2 : PdfDictionary
+ public sealed class PdfSignature : PdfDictionary
{
+ /////
+ ///// Initializes a new instance of the class.
+ /////
+ //public PdfSignature()
+ //{ }
+
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public PdfSignature2()
+ public PdfSignature(PdfDocument document) : base(document)
{ }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
- public PdfSignature2(PdfDocument dict) : base(dict)
+ internal PdfSignature(PdfDictionary dict) : base(dict)
{ }
/////
@@ -29,5 +38,86 @@ public PdfSignature2(PdfDocument dict) : base(dict)
///// Decrypts the given bytes. Returns true if the crypt filter decrypted the bytes, or false, if the security handler shall do it.
/////
//internal abstract bool DecryptForEnteredObject(ref byte[] bytes);
+
+ ///
+ /// Predefined keys of this dictionary.
+ /// The description comes from PDF 1.4 Reference.
+ ///
+ public class Keys : KeysBase
+ {
+ ///
+ /// (Optional) The type of PDF object that this dictionary describes; if present,
+ /// must be Sig for a signature dictionary.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Type = "/Type";
+
+ ///
+ /// (Required; inheritable) The name of the signature handler to be used for
+ /// authenticating the field’s contents, such as Adobe.PPKLite, Entrust.PPKEF,
+ /// CICI.SignIt, or VeriSign.PPKVS.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Required)]
+ public const string Filter = "/Filter";
+
+ ///
+ /// (Optional) The name of a specific submethod of the specified handler.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string SubFilter = "/SubFilter";
+
+ ///
+ /// (Required) An array of pairs of integers (starting byte offset, length in bytes)
+ /// describing the exact byte range for the digest calculation. Multiple discontinuous
+ /// byte ranges may be used to describe a digest that does not include the
+ /// signature token itself.
+ ///
+ [KeyInfo(KeyType.Array | KeyType.Required)]
+ public const string ByteRange = "/ByteRange";
+
+ ///
+ /// (Required) The encrypted signature token.
+ ///
+ [KeyInfo(KeyType.String | KeyType.Required)]
+ public const string Contents = "/Contents";
+
+ ///
+ /// (Optional) The name of the person or authority signing the document.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string Name = "/Name";
+
+ ///
+ /// (Optional) The time of signing. Depending on the signature handler, this
+ /// may be a normal unverified computer time or a time generated in a verifiable
+ /// way from a secure time server.
+ ///
+ [KeyInfo(KeyType.Date | KeyType.Optional)]
+ public const string M = "/M";
+
+ ///
+ /// (Optional) The CPU host name or physical location of the signing.
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string Location = "/Location";
+
+ ///
+ /// (Optional) The reason for the signing, such as (I agree…).
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string Reason = "/Reason";
+
+ ///
+ /// Gets the KeysMeta for these keys.
+ ///
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+
+ static DictionaryMeta? _meta;
+ }
+
+ ///
+ /// Gets the KeysMeta of this dictionary type.
+ ///
+ internal override DictionaryMeta Meta => Keys.Meta;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs
deleted file mode 100644
index 64c6497f..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs
+++ /dev/null
@@ -1,243 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-#if !NET6_0_OR_GREATER
-using System.Text;
-#endif
-using PdfSharp.Pdf.AcroForms;
-using PdfSharp.Pdf.Advanced;
-using PdfSharp.Pdf.Internal;
-using PdfSharp.Pdf.IO;
-
-namespace PdfSharp.Pdf.Signatures
-{
- ///
- /// PdfDocument signature handler.
- /// Attaches a PKCS#7 signature digest to PdfDocument.
- ///
- // DigitalSignatureHandler rename file
- public class DigitalSignatureHandler
- {
- ///
- /// Space ... big enough reserved space to replace ByteRange placeholder [0 0 0 0] with the actual computed value of the byte range to sign
- /// Worst case: signature dictionary is near the end of an 10 GB PDF file.
- ///
- const int ByteRangePaddingLength = 36; // = "[0 9999999999 9999999999 9999999999]".Length
-
- DigitalSignatureHandler(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options)
- {
- Document = document ?? throw new ArgumentNullException(nameof(document));
- Signer = signer ?? throw new ArgumentNullException(nameof(signer));
- Options = options ?? throw new ArgumentNullException(nameof(options));
-
- if (options.PageIndex < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(options.PageIndex),
- "Signature page index cannot be negative.");
- }
-
- // TODO_OLD in document: Set document version depending on digest type from options.
- }
-
- ///
- /// Gets or creates the digital signature handler for the specified document.
- ///
- public static DigitalSignatureHandler ForDocument(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options)
- {
- return document._digitalSignatureHandler ??= new(document, signer, options);
- }
-
-
- ///
- /// Gets the PDF document the signature will be attached to.
- ///
- public PdfDocument Document { get; init; }
-
- ///
- /// Gets the options for the digital signature.
- ///
- public DigitalSignatureOptions Options { get; init; }
-
- IDigitalSigner Signer { get; init; }
-
- internal async Task ComputeSignatureAndRange(PdfWriter writer)
- {
- var (rangedStreamToSign, byteRangeArray) = GetRangeToSignAndByteRangeArray(writer.Stream);
-
- Debug.Assert(_signatureFieldByteRangePdfArray != null);
- writer.Stream.Position = _signatureFieldByteRangePdfArray.StartPosition;
- byteRangeArray.WriteObject(writer);
-
- // Computing signature from document’s digest.
- var signature = await Signer.GetSignatureAsync(rangedStreamToSign).ConfigureAwait(false);
-
- Debug.Assert(_placeholderItem != null);
- int expectedLength = _placeholderItem.Size;
- if (signature.Length > expectedLength)
- throw new Exception($"The actual digest length {signature.Length} is larger than the approximation made {expectedLength}. Not enough room in the placeholder to fit the signature.");
-
- // Write the signature at the space reserved by placeholder item.
- writer.Stream.Position = _placeholderItem.StartPosition;
-
- // When the signature includes a timestamp, the exact length is unknown until the signature is definitely calculated.
- // Therefore, we write the angle brackets here and override the placeholder white spaces.
- writer.WriteRaw('<');
- writer.Write(PdfEncoders.RawEncoding.GetBytes(FormatHex(signature)));
-
- // Fill up the allocated placeholder. Signature is sometimes considered invalid if there are spaces after '>'.
- for (int x = signature.Length; x < expectedLength; ++x)
- writer.WriteRaw("00");
-
- writer.WriteRaw('>');
- }
-
- string FormatHex(byte[] bytes) // ...use RawEncoder
- {
-#if NET6_0_OR_GREATER
- return Convert.ToHexString(bytes);
-#else
- var result = new StringBuilder();
-
- for (int idx = 0; idx < bytes.Length; idx++)
- result.AppendFormat("{0:X2}", bytes[idx]);
-
- return result.ToString();
-#endif
- }
-
- ///
- /// Get the bytes ranges to sign.
- /// As recommended in PDF specs, whole document will be signed, except for the hexadecimal signature token value in the /Contents entry.
- /// Example: '/Contents <aaaaa111111>' => '<aaaaa111111>' will be excluded from the bytes to sign.
- ///
- ///
- (RangedStream rangedStream, PdfArray byteRangeArray) GetRangeToSignAndByteRangeArray(Stream stream)
- {
- Debug.Assert( _placeholderItem !=null, nameof(_placeholderItem) + " must not be null here.");
-
- SizeType firstRangeOffset = 0;
- SizeType firstRangeLength = _placeholderItem.StartPosition;
- SizeType secondRangeOffset = _placeholderItem.EndPosition;
- SizeType secondRangeLength = stream.Length - _placeholderItem.EndPosition;
-
- var byteRangeArray = new PdfArray();
- byteRangeArray.Elements.Add(new PdfLongInteger(firstRangeOffset));
- byteRangeArray.Elements.Add(new PdfLongInteger(firstRangeLength));
- byteRangeArray.Elements.Add(new PdfLongInteger(secondRangeOffset));
- byteRangeArray.Elements.Add(new PdfLongInteger(secondRangeLength));
-
- var rangedStream = new RangedStream(stream,
- [
- new(firstRangeOffset, firstRangeLength),
- new(secondRangeOffset, secondRangeLength)
- ]);
-
- return (rangedStream, byteRangeArray);
- }
-
- ///
- /// Adds the PDF objects required for a digital signature.
- ///
- ///
- internal async Task AddSignatureComponentsAsync()
- {
- if (Options.PageIndex >= Document.PageCount)
- throw new ArgumentOutOfRangeException($"Signature page doesn't exist, specified page was {Options.PageIndex + 1} but document has only {Document.PageCount} page(s).");
-
- var signatureSize = await Signer.GetSignatureSizeAsync().ConfigureAwait(false);
- _placeholderItem = new(signatureSize);
- _signatureFieldByteRangePdfArray = new PdfArrayWithPadding(Document, ByteRangePaddingLength, new PdfLongInteger(0), new PdfLongInteger(0), new PdfLongInteger(0), new PdfLongInteger(0));
-
- var signatureDictionary = GetSignatureDictionary(_placeholderItem, _signatureFieldByteRangePdfArray);
- var signatureField = GetSignatureField(signatureDictionary);
-
- var annotations = Document.Pages[Options.PageIndex].Elements.GetArray(PdfPage.Keys.Annots);
- if (annotations == null)
- Document.Pages[Options.PageIndex].Elements.Add(PdfPage.Keys.Annots, new PdfArray(Document, signatureField));
- else
- annotations.Elements.Add(signatureField);
-
- // acroform
-
- var catalog = Document.Catalog;
-
- if (catalog.Elements.GetObject(PdfCatalog.Keys.AcroForm) == null)
- catalog.Elements.Add(PdfCatalog.Keys.AcroForm, new PdfAcroForm(Document));
-
- if (!catalog.AcroForm.Elements.ContainsKey(PdfAcroForm.Keys.SigFlags))
- catalog.AcroForm.Elements.Add(PdfAcroForm.Keys.SigFlags, new PdfInteger(3));
- else
- {
- var sigFlagVersion = catalog.AcroForm.Elements.GetInteger(PdfAcroForm.Keys.SigFlags);
- if (sigFlagVersion < 3)
- catalog.AcroForm.Elements.SetInteger(PdfAcroForm.Keys.SigFlags, 3);
- }
-
- if (catalog.AcroForm.Elements.GetValue(PdfAcroForm.Keys.Fields) == null)
- catalog.AcroForm.Elements.SetValue(PdfAcroForm.Keys.Fields, new PdfAcroField.PdfAcroFieldCollection(new PdfArray()));
- catalog.AcroForm.Fields.Elements.Add(signatureField);
- }
-
- PdfSignatureField GetSignatureField(PdfSignature2 signatureDic)
- {
- var signatureField = new PdfSignatureField(Document);
-
- signatureField.Elements.Add(PdfAcroField.Keys.V, signatureDic);
-
- // Annotation keys.
- signatureField.Elements.Add(PdfAcroField.Keys.FT, new PdfName("/Sig"));
- signatureField.Elements.Add(PdfAcroField.Keys.T, new PdfString("Signature1")); // TODO_OLD If already exists, will it cause error? implement a name chooser if yes.
- signatureField.Elements.Add(PdfAcroField.Keys.Ff, new PdfInteger(132));
- signatureField.Elements.Add(PdfAcroField.Keys.DR, new PdfDictionary());
- signatureField.Elements.Add(PdfSignatureField.Keys.Type, new PdfName("/Annot"));
- signatureField.Elements.Add("/Subtype", new PdfName("/Widget"));
- signatureField.Elements.Add("/P", Document.Pages[Options.PageIndex]);
-
- signatureField.Elements.Add("/Rect", new PdfRectangle(Options.Rectangle));
-
- signatureField.CustomAppearanceHandler = Options.AppearanceHandler ?? new DefaultSignatureAppearanceHandler()
- {
- Location = Options.Location,
- Reason = Options.Reason,
- Signer = Signer.CertificateName
- };
- // TODO_OLD Call RenderCustomAppearance(); here.
- signatureField.PrepareForSave(); // TODO_OLD PdfSignatureField.PrepareForSave() is not triggered automatically so let's call it manually from here, but it would be better to be called automatically.
-
- Document.Internals.AddObject(signatureField);
-
- return signatureField;
- }
-
- PdfSignature2 GetSignatureDictionary(PdfSignaturePlaceholderItem contents, PdfArray byteRange)
- {
- PdfSignature2 signatureDic = new(Document);
-
- signatureDic.Elements.Add(PdfSignatureField.Keys.Type, new PdfName("/Sig"));
- signatureDic.Elements.Add(PdfSignatureField.Keys.Filter, new PdfName("/Adobe.PPKLite"));
- signatureDic.Elements.Add(PdfSignatureField.Keys.SubFilter, new PdfName("/adbe.pkcs7.detached"));
- signatureDic.Elements.Add(PdfSignatureField.Keys.M, new PdfDate(DateTime.Now));
-
- signatureDic.Elements.Add(PdfSignatureField.Keys.Contents, contents);
- signatureDic.Elements.Add(PdfSignatureField.Keys.ByteRange, byteRange);
- signatureDic.Elements.Add(PdfSignatureField.Keys.Reason, new PdfString(Options.Reason));
- signatureDic.Elements.Add(PdfSignatureField.Keys.Location, new PdfString(Options.Location));
-
- var properties = new PdfDictionary(Document);
- signatureDic.Elements.Add("/Prop_Build", properties);
- var propertyItems = new PdfDictionary(Document);
- properties.Elements.Add("/App", propertyItems);
- propertyItems.Elements.Add("/Name",
- String.IsNullOrWhiteSpace(Options.AppName) ?
- new PdfName("/PDFsharp http://www.pdfsharp.net") :
- PdfName.FromString(Options.AppName));
-
- Document.Internals.AddObject(signatureDic);
-
- return signatureDic;
- }
-
- PdfSignaturePlaceholderItem? _placeholderItem;
- PdfArrayWithPadding? _signatureFieldByteRangePdfArray;
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignaturePlaceholderItem.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignaturePlaceholderItem.cs
deleted file mode 100644
index 7f0d2ab2..00000000
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignaturePlaceholderItem.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-// PDFsharp - A .NET library for processing PDF
-// See the LICENSE file in the solution root for more information.
-
-using PdfSharp.Pdf.IO;
-
-namespace PdfSharp.Pdf.Signatures
-{
- ///
- /// Initializes a new instance of the class.
- /// It represents a placeholder for a digital signature.
- /// Note that the placeholder must be completely overridden, if necessary the signature
- /// must be padded with trailing zeros. Blanks between the end of the hex-sting and
- /// the end of the reserved space is considered as a certificate error by Acrobat.
- ///
- /// The size of the signature in bytes.
- [DebuggerDisplay("({" + nameof(Size) + "})")]
- sealed class PdfSignaturePlaceholderItem(int size) : PdfItem
- {
- ///
- /// Returns the placeholder string padded with question marks to ensure that the code
- /// fails if it is not correctly overridden.
- ///
- public override string ToString() => "<" + new string('?', 2 * Size)+ ">";
-
- ///
- /// Writes the item DocEncoded.
- ///
- internal override void WriteObject(PdfWriter writer)
- => writer.Write(this, out _startPosition, out _endPosition);
-
- ///
- /// Gets the number of bytes of the signature.
- ///
- public int Size { get; init; } = size;
-
- ///
- /// Position of the first byte of this item in PdfWriter’s stream.
- /// Precisely: The index of the '<'.
- ///
- public SizeType StartPosition => _startPosition;
- SizeType _startPosition;
-
- ///
- /// Position of the last byte of this item in PdfWriter’s stream.
- /// Precisely: The index of the line feed behind '>'.
- /// For timestamped signatures, the maximum length must be used.
- ///
- public SizeType EndPosition => _endPosition;
- SizeType _endPosition;
- }
-}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/RangedStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/RangedStream.cs
index 0746144e..01ed3178 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/RangedStream.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/RangedStream.cs
@@ -1,6 +1,10 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+// v7.0.0 TODO review
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
namespace PdfSharp.Pdf.Signatures
{
///
@@ -8,15 +12,8 @@ namespace PdfSharp.Pdf.Signatures
/// It is based on a stream plus a collection of ranges that define the significant content of this stream.
/// The ranges are used to exclude one or more areas of the original stream.
///
- class RangedStream : Stream // StL: Can I say 'RangedStream' in English? SlicedStream?
+ class RangedStream : Stream // StL: Can I say 'RangedStream' in English? SlicedStream? TODO SlicesdStream
{
- internal class Range(SizeType offset, SizeType length)
- {
- public SizeType Offset { get; set; } = offset;
-
- public SizeType Length { get; set; } = length;
- }
-
public RangedStream(Stream originalStream, List ranges)
{
if (originalStream.CanRead != true)
@@ -45,10 +42,10 @@ public RangedStream(Stream originalStream, List ranges)
public override long Length => _ranges.Sum(item => item.Length);
- private IEnumerable GetPreviousRanges(long position)
+ IEnumerable GetPreviousRanges(long position)
=> _ranges.Where(item => item.Offset < position && item.Offset + item.Length < position);
- private Range? GetCurrentRange(long position)
+ Range? GetCurrentRange(long position)
=> _ranges.FirstOrDefault(item => item.Offset <= position && item.Offset + item.Length > position);
public override long Position
@@ -74,7 +71,8 @@ public override long Position
}
}
- public override void Flush() => throw new NotImplementedException(nameof(Flush));
+ public override void Flush()
+ => throw new NotImplementedException(nameof(Flush));
public override int Read(byte[] buffer, int offset, int count)
{
@@ -104,9 +102,9 @@ public override int Read(byte[] buffer, int offset, int count)
// We come here e.g. with Bouncy Castle signer.
// We calculate the current range for each byte in the stream using LINQ.
- // This works, but is very slow. If we get performance issues
+ // This works, but is very inefficient. If we get performance issues
// it should be reimplemented by using the ranges here.
- // But this works, so YAGNI.
+ // But this is correct and works, so YAGNI.
for (int i = 0; i < count; i++)
{
if (Stream.Position == length)
@@ -128,14 +126,87 @@ void PerformSkipIfNeeded()
Range GetNextRange()
=> _ranges.OrderBy(item => item.Offset).First(item => item.Offset > Stream.Position);
- public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(nameof(Seek));
+ public override long Seek(long offset, SeekOrigin origin)
+ => throw new NotImplementedException(nameof(Seek));
- public override void SetLength(long value) => throw new NotImplementedException(nameof(SetLength));
+ public override void SetLength(long value)
+ => throw new NotImplementedException(nameof(SetLength));
- public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(nameof(Write));
+ public override void Write(byte[] buffer, int offset, int count)
+ => throw new NotImplementedException(nameof(Write));
readonly Range[] _ranges;
Stream Stream { get; }
+
+ internal class Range(SizeType offset, SizeType length)
+ {
+ public SizeType Offset { get; set; } = offset;
+
+ public SizeType Length { get; set; } = length;
+ }
+ }
+}
+
+namespace PdfSharp.Pdf.Internal
+{
+ public class ManagerBase
+ {
+ public ManagerBase(PdfDocument document)
+ {
+ if (document == null!)
+ throw new ArgumentNullException(nameof(document));
+ Document = document;
+ }
+
+ internal void Foo2() { }
+
+ internal bool IsInitialized;
+
+ internal PdfDocument Document { get; }
+ }
+}
+
+namespace PdfSharp.Pdf.Signatures
+{
+ using PdfSharp.Pdf.Internal;
+
+ ///
+ /// The PdfAManager bundles PDF/A specific functionality of a PDF document.
+ ///
+ public class SigningManager : ManagerBase // RENAME?
+ {
+ // TODOs:
+
+ ///
+ /// Initialized a new instance of this class for the specified document
+ ///
+ ///
+ SigningManager(PdfDocument document) : base(document)
+ {
+ Initialize();
+ }
+
+ public string SignatureType { get; } = "(TODO)"; // TODO
+
+ ///
+ /// Gets or creates the PdfAManager for the specified document.
+ ///
+ public static SigningManager ForDocument(PdfDocument document)
+ => document.SigningManager ??= new(document);
+
+ void Initialize()
+ {
+ Document.EnsureNotDisposed();
+ if (!IsInitialized)
+ {
+ IsInitialized = true;
+ Document.EnsureNotYetSaved();
+ if (Document.IsImported)
+ {
+ // TODO: Get PDF/A conformance
+ }
+ }
+ }
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfAttributesBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfAttributesBase.cs
index a39c5243..81ef95de 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfAttributesBase.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfAttributesBase.cs
@@ -8,6 +8,12 @@ namespace PdfSharp.Pdf.Structure
///
public abstract class PdfAttributesBase : PdfDictionary
{
+ ///
+ /// Constructor of the abstract class.
+ ///
+ protected PdfAttributesBase()
+ { }
+
///
/// Constructor of the abstract class.
///
@@ -17,9 +23,11 @@ internal PdfAttributesBase(PdfDocument document)
{ }
///
- /// Constructor of the abstract class.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
- protected PdfAttributesBase()
+ internal PdfAttributesBase(PdfDictionary dict)
+ : base(dict)
{ }
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfLayoutAttributes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfLayoutAttributes.cs
index 58ff0b13..2c9fa7f6 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfLayoutAttributes.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfLayoutAttributes.cs
@@ -18,6 +18,16 @@ internal PdfLayoutAttributes(PdfDocument document)
SetOwner();
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfLayoutAttributes(PdfDictionary dict)
+ : base(dict)
+ {
+ SetOwner();
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkInformation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkInformation.cs
index 47d1e933..9841db90 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkInformation.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkInformation.cs
@@ -22,6 +22,14 @@ public PdfMarkInformation(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfMarkInformation(PdfDictionary dict)
+ : base(dict)
+ { }
+
///
/// Predefined keys of this dictionary.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkedContentReference.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkedContentReference.cs
index e0fabff2..3e4d58ef 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkedContentReference.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkedContentReference.cs
@@ -26,6 +26,16 @@ public PdfMarkedContentReference(PdfDocument document)
Elements.SetName(Keys.Type, "/MCR");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfMarkedContentReference(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.Type, "/MCR");
+ }
+
///
/// Predefined keys of this dictionary.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfObjectReference.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfObjectReference.cs
index 7083e785..c9a800ae 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfObjectReference.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfObjectReference.cs
@@ -26,6 +26,16 @@ public PdfObjectReference(PdfDocument document)
Elements.SetName(Keys.Type, "/OBJR");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfObjectReference(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.Type, "/OBJR");
+ }
+
///
/// Predefined keys of this dictionary.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureElement.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureElement.cs
index 2886964e..9b487e84 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureElement.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureElement.cs
@@ -28,6 +28,16 @@ public PdfStructureElement(PdfDocument document)
Elements.SetName(Keys.Type, "/StructElem");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfStructureElement(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.Type, "/StructElem");
+ }
+
internal override void PrepareForSave()
{
SimplifyKidsArray();
@@ -44,6 +54,7 @@ internal static IEnumerable GetKids(DictionaryElements? elements)
{
if (elements != null)
{
+ //TODO: #warning What is the expectation GetObject should return?
var k = elements.GetObject(Keys.K);
// If k is holding an array, return all elements.
@@ -80,7 +91,8 @@ internal static IEnumerable GetKids(DictionaryElements? elements)
///
void SimplifyKidsArray()
{
- if (Elements[Keys.K] is PdfArray k && k.Elements.Count == 1)
+ //if (Elements[Keys.K] is PdfArray k && k.Elements.Count == 1)
+ if (Elements.GetArray(Keys.K) is { Elements.Count: 1 } k) // #US373
{
var item = k.Elements[0];
Elements[Keys.K] = item;
@@ -92,7 +104,8 @@ void SimplifyKidsArray()
///
void SimplifyAttributes()
{
- var a = Elements[Keys.A];
+ //var a = Elements[Keys.A];
+ var a = Elements.GetValue(Keys.A); // #US373
if (a is PdfArray array)
{
@@ -145,7 +158,8 @@ bool AttributeDictionaryIsEmpty(PdfDictionary dictionary)
T GetAttributes() where T : PdfAttributesBase, new()
{
- var a = Elements[Keys.A];
+ //var a = Elements[Keys.A];
+ var a = Elements.GetValue(Keys.A); // #US373
var array = a as PdfArray;
if (array == null)
{
@@ -165,7 +179,7 @@ bool AttributeDictionaryIsEmpty(PdfDictionary dictionary)
return item;
}
- // Create and add a new instance of T, if there’s no one.
+ // Create and add a new instance of T, if there’s none.
var t = new T { Document = Owner };
array.Elements.Add(t);
return t;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureTreeRoot.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureTreeRoot.cs
index 0a54a11d..cfb395d3 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureTreeRoot.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureTreeRoot.cs
@@ -28,6 +28,16 @@ public PdfStructureTreeRoot(PdfDocument document)
Elements.SetName(Keys.Type, "/StructTreeRoot");
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfStructureTreeRoot(PdfDictionary dict)
+ : base(dict)
+ {
+ Elements.SetName(Keys.Type, "/StructTreeRoot");
+ }
+
internal override void PrepareForSave()
{
foreach (var k in PdfStructureElement.GetKids(Elements))
@@ -71,7 +81,7 @@ internal class Keys : KeysBase
/// Each integer key in the number tree corresponds to a single page of the
/// document or to an individual object (such as an annotation or an XObject)
/// that is a content item in its own right. The integer key is given as the
- /// value of the StructParent or StructParents entry in that object.
+ /// value of the ParentInfo or StructParents entry in that object.
/// The form of the associated value depends on the nature of the object:
/// • For an object that is a content item in its own right, the value is an
/// indirect reference to the object’s parent element (the structure element
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfTableAttributes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfTableAttributes.cs
index 626e1114..e18d52a5 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfTableAttributes.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfTableAttributes.cs
@@ -18,6 +18,16 @@ internal PdfTableAttributes(PdfDocument document)
SetOwner();
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfTableAttributes(PdfDictionary dict)
+ : base(dict)
+ {
+ SetOwner();
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ArrayOrSingleItemHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ArrayOrSingleItemHelper.cs
index 9b0fc35f..0ef4d6db 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ArrayOrSingleItemHelper.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ArrayOrSingleItemHelper.cs
@@ -1,12 +1,14 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Pdf.Advanced;
+
namespace PdfSharp.Pdf
{
///
/// Provides methods to handle keys that may contain a PdfArray or a single PdfItem.
///
- public class ArrayOrSingleItemHelper
+ public class ArrayOrSingleItemHelper // #US373 StL: review
{
///
/// Initializes ArrayOrSingleItemHelper with PdfDictionary.DictionaryElements to work with.
@@ -14,7 +16,7 @@ public class ArrayOrSingleItemHelper
public ArrayOrSingleItemHelper(PdfDictionary.DictionaryElements elements)
{
_elements = elements;
- _dictionary = elements.Owner;
+ _dictionary = elements.OwningDictionary;
}
///
@@ -26,21 +28,33 @@ public ArrayOrSingleItemHelper(PdfDictionary.DictionaryElements elements)
/// True, if value shall be prepended instead of appended.
public void Add(string key, PdfItem value, bool prepend = false)
{
- var obj = _elements[key];
+ // #US373
+ if (value is PdfReference reference)
+ {
+ if (reference.Value != null!)
+ {
+ value = reference.Value;
+ // Error
+ }
+ }
- var array = obj as PdfArray;
+ //var oldValue = _elements[key]; // #US373: Should we expect references here?
+ var oldValue = _elements.GetValue(key); // #US373
+ // oldValue can be null, a PDF item, or an array.
+ var array = oldValue as PdfArray;
// If key is not yet set or key contains array without elements, assign value directly to key.
- if (obj is null || array is not null && array.Elements.Count == 0)
+ if (oldValue is null || array is not null && array.Elements.Count == 0)
{
_elements[key] = value;
return;
}
- // If not yet existing, create an array and assign the current directly assigned obj.
+ // If array is not yet existing, create one and assign the current directly assigned oldValue.
if (array is null)
{
- array = new PdfArray(_dictionary._document, obj);
+ // oldValue is not null here.
+ array = new PdfArray(_dictionary.Document, oldValue);
_elements[key] = array;
}
@@ -57,21 +71,26 @@ public void Add(string key, PdfItem value, bool prepend = false)
/// The key in the dictionary to work with.
public IEnumerable GetAll(string key)
{
- var obj = _elements[key];
-
- if (obj is PdfArray array)
+ var value = _elements.GetValue(key);
+ if (value is PdfArray array)
{
foreach (var item in array.Elements)
yield return item;
}
- else if (obj is not null)
- yield return obj;
+ else
+ {
+ if (value is not null)
+ yield return value;
+ else
+ {
+ // TODO: Breaking change. Empty enumeration instead of null item.
+ yield break;
+ }
+ }
}
IEnumerable Get(string key, Func predicate)
- {
- return GetAll(key).Where(predicate);
- }
+ => GetAll(key).Where(predicate);
///
/// Gets the PdfItem(s) of type T saved in the given key, that match a predicate.
@@ -79,9 +98,7 @@ IEnumerable Get(string key, Func predicate)
/// The key in the dictionary to work with.
/// The predicate, that shall be true for the desired item(s).
public IEnumerable Get(string key, Func predicate) where T : PdfItem
- {
- return Get(key, x => x is T xT && predicate(xT)).Cast();
- }
+ => Get(key, x => x is T xT && predicate(xT)).Cast();
///
/// Gets the PdfItem(s) of type T saved in the given key, that are equal to value.
@@ -90,9 +107,7 @@ public IEnumerable Get(string key, Func predicate) where T : PdfI
/// The value, the desired item(s) shall be equal to.
// Allows to call Equals with object.
public IEnumerable Get(string key, object value) where T : PdfItem
- {
- return Get(key, x => x.Equals(value));
- }
+ => Get(key, x => x.Equals(value));
///
/// Gets the PdfItem(s) of type T saved in the given key, that are equal to value.
@@ -101,9 +116,7 @@ public IEnumerable Get(string key, object value) where T : PdfItem
/// The value, the desired item(s) shall be equal to.
// Allows to omit the type parameter in the call.
public IEnumerable Get(string key, T value) where T : PdfItem
- {
- return Get(key, x => x.Equals(value));
- }
+ => Get(key, x => x.Equals(value));
///
/// Returns true if the given key contains a PdfItem of type T matching a predicate.
@@ -111,9 +124,7 @@ public IEnumerable Get(string key, T value) where T : PdfItem
/// The key in the dictionary to work with.
/// The predicate, that shall be true for the desired item(s).
public bool Contains(string key, Func predicate) where T : PdfItem
- {
- return Get(key, x => x is T xT && predicate(xT)).Any();
- }
+ => Get(key, x => x is T xT && predicate(xT)).Any();
///
/// Returns true if the given key contains a PdfItem of type T, that is equal to value.
@@ -122,9 +133,7 @@ public bool Contains(string key, Func predicate) where T : PdfItem
/// The value, the desired item(s) shall be equal to.
// Allows to call Equals with object.
public bool Contains(string key, object value) where T : PdfItem
- {
- return Contains(key, x => x.Equals(value));
- }
+ => Contains(key, x => x.Equals(value));
///
/// Returns true if the given key contains a PdfItem of type T, that is equal to value.
@@ -133,13 +142,12 @@ public bool Contains(string key, object value) where T : PdfItem
/// The value, the desired item(s) shall be equal to.
// Allows to omit the type parameter in the call.
public bool Contains(string key, T value) where T : PdfItem
- {
- return Contains(key, x => x.Equals(value));
- }
+ => Contains(key, x => x.Equals(value));
bool Remove(string key, Func predicate)
{
- var obj = _elements[key];
+ //var obj = _elements[key]; // #US373: Should we expect references here?
+ var obj = _elements.GetValue(key); // #US373
if (obj is null)
return false;
@@ -186,9 +194,7 @@ bool Remove(string key, Func predicate)
/// The key in the dictionary to work with.
/// The predicate, that shall be true for the desired item(s).
public bool Remove(string key, Func predicate) where T : PdfItem
- {
- return Remove(key, x => x is T xT && predicate(xT));
- }
+ => Remove(key, x => x is T xT && predicate(xT));
///
/// Removes the PdfItem(s) of type T saved in the given key, that are equal to value.
@@ -199,9 +205,7 @@ public bool Remove(string key, Func predicate) where T : PdfItem
/// The value, the desired item(s) shall be equal to.
// Allows to call Equals with object.
public bool Remove(string key, object value) where T : PdfItem
- {
- return Remove(key, x => x.Equals(value));
- }
+ => Remove(key, x => x.Equals(value));
///
/// Removes the PdfItem(s) of type T saved in the given key, that are equal to value.
@@ -212,9 +216,7 @@ public bool Remove(string key, object value) where T : PdfItem
/// The value, the desired item(s) shall be equal to.
// Allows to omit the type parameter in the call.
public bool Remove(string key, T value) where T : PdfItem
- {
- return Remove(key, x => x.Equals(value));
- }
+ => Remove(key, x => x.Equals(value));
readonly PdfDictionary _dictionary;
readonly PdfDictionary.DictionaryElements _elements;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ElementsBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ElementsBase.cs
new file mode 100644
index 00000000..495e3500
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ElementsBase.cs
@@ -0,0 +1,307 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace PdfSharp.Pdf
+{
+ ///
+ /// Base class for both ArrayElements and DictionaryElements.
+ ///
+ /// The PdfContainer this instance belongs to.
+ public abstract class ElementsBase(PdfContainer owningContainer)
+ {
+ ///
+ /// Gets the PDF array or dictionary the elements belong to.
+ ///
+ public PdfContainer OwningContainer { get; internal set; } = owningContainer;
+
+ ///
+ /// Marks a PDF item as 'not in use' anymore.
+ /// For a PDF reference or an indirect object the reference counter is decremented.
+ /// (Note that the reference counter is not yet used in PDFsharp.)
+ /// For a direct object the structure parent is set to null and the object can be
+ /// reused as a direct object or became an indirect object.
+ ///
+ protected static void ReleaseItem(PdfItem item)
+ {
+ //if (item == null)
+ // return;
+ if (item is PdfObject obj)
+ {
+ var reference = obj.Reference;
+ if (reference != null)
+ {
+ Debug.Assert(obj.ParentInfo == null, "Indirect PDF object must not have a ParentInfo.");
+ reference.Release();
+ }
+ else
+ {
+ Debug.Assert(obj.ParentInfo != null, "Direct PDF object must have a ParentInfo.");
+ obj.SetStructureParentNull();
+ // The object is not dead and can be reused.
+ }
+ }
+ else if (item is PdfReference reference)
+ {
+ // reference.Value can be null during reading a PDF document.
+ Debug.Assert(reference.Value?.ParentInfo == null, "Indirect PDF object must not have a ParentInfo.");
+ reference.Release();
+ }
+ }
+
+ ///
+ /// Creates a container of the specified type depending on an optional old container.
+ ///
+ /// The type of the object to be created.
+ /// Container its elements are moved to the newly created type.
+ /// If true creates an indirect object.
+ protected internal PdfContainer CreateContainer(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type type, PdfContainer? oldContainer, bool createIndirect)
+ {
+#if DEBUG_
+ //if (ShouldBreak1 || oldContainer?.Aaa == "ABC")
+ // Debugger.Break();
+ if (type == typeof(PdfFormXObject))
+ _ = typeof(int);
+#endif
+ PdfContainer cont;
+ if (oldContainer == null)
+ {
+ // Try to get constructor with signature 'Ctor(PdfDocument, bool)'.
+ var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, [typeof(PdfDocument), typeof(bool)], null);
+
+ if (ctorInfo != null)
+ {
+ cont = (PdfContainer)ctorInfo.Invoke([OwningContainer.Owner, false]);
+ }
+ else
+ {
+ // Try to get constructor with signature 'Ctor(PdfDocument)'.
+ ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, [typeof(PdfDocument)], null);
+ if (ctorInfo != null)
+ {
+ cont = (PdfContainer)ctorInfo.Invoke([OwningContainer.Owner]);
+ }
+ else
+ {
+ // Try to get with signature 'Ctor()'.
+ ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, [], null);
+
+ if (ctorInfo != null)
+ {
+ cont = (PdfContainer)ctorInfo.Invoke([]);
+ }
+ else
+ throw new InvalidOperationException($"No appropriate constructor found for type {type.FullName}.");
+ }
+ }
+ Debug.Assert(cont?.IsTransformed is false);
+ cont.SetTransformed();
+ }
+ else
+ {
+ if (type.IsInstanceOfType(oldContainer))
+ Debug.Assert(false, "Should not happen.");
+
+ ConstructorInfo? ctorInfo;
+ if (typeof(PdfDictionary).IsAssignableFrom(type))
+ {
+ // Use constructor with signature 'Ctor(PdfDictionary array)'.
+ ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, types: [typeof(PdfDictionary)], null);
+ }
+ else if (typeof(PdfArray).IsAssignableFrom(type))
+ {
+ // Use constructor with signature 'Ctor(PdfArray dict)'.
+ ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, types: [typeof(PdfArray)], null);
+ }
+ else
+ {
+ if (type == typeof(PdfRectangle))
+ {
+ // IMPROVE see US291
+ throw new InvalidOperationException("PdfRectangle is not implemented as an array, try use GetRectangle.");
+ }
+ throw new InvalidOperationException($"Type {type.Name} is not allowed here");
+ }
+
+ Debug.Assert(ctorInfo != null, $"No appropriate constructor found for type: {type.Name}.");
+ cont = (PdfContainer)ctorInfo.Invoke([oldContainer]);
+ //Debug.Assert(dict?.IsTransformed is false);
+ if (cont?.IsTransformed == false)
+ {
+ Debugger.Break();
+ cont?.SetTransformed();
+ }
+ }
+ Debug.Assert(cont?.IsTransformed is true);
+ return cont ?? NRT.ThrowOnNull();
+ }
+
+#if true_ // KEEP for reference DELETE 25-12-31
+ protected PdfArray CreateArray(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type type, PdfArray? oldArray)
+ {
+#if DEBUG_
+ if (ShouldBreak1 || oldArray?.Aaa == "Stefan")
+ Debugger.Break();
+#endif
+ // PagesArray is a PdfArray.
+ //Debug.Assert(type != typeof(PdfArray));
+ Debug.Assert(typeof(PdfArray).IsAssignableFrom(type));
+
+ PdfArray? array;
+ if (oldArray == null)
+ {
+ // Try constructor with signature 'Ctor(PdfDocument owner)'.
+ var ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, [typeof(PdfDocument)], null);
+#if true
+ if (ctorInfo != null)
+ {
+ array = ctorInfo.Invoke([OwningContainer.Owner]) as PdfArray;
+ }
+ else
+ {
+ // Try constructor with signature 'Ctor()'.
+ ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, [], null);
+
+ if (ctorInfo == null)
+ throw new InvalidOperationException(
+ $"No appropriate constructor found for type {type.FullName}.");
+ array = ctorInfo.Invoke([]) as PdfArray;
+ }
+ Debug.Assert(array?.IsTransformed is false);
+ array.SetTransformed();
+#else // DELETE
+ Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name);
+ array = ctorInfo.Invoke([OwningContainer.Owner]) as PdfArray;
+#endif
+ }
+ else
+ {
+ // Use constructor with signature 'Ctor(PdfArray array)'.
+ var ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, types: [typeof(PdfArray)], null);
+ if (ctorInfo == null)
+ throw new InvalidOperationException($"No appropriate constructor found for type {type.FullName}.");
+
+ array = ctorInfo.Invoke([oldArray]) as PdfArray;
+ //Debug.Assert(array?.IsTransformed is false);
+ array?.SetTransformed();
+ }
+ return array ?? NRT.ThrowOnNull();
+ }
+#endif
+
+#if true_ // KEEP for reference
+ protected PdfDictionary CreateDictionary(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type type, PdfDictionary? oldDictionary)
+ {
+ //Debug.Assert(type != typeof(PdfDictionary));
+ Debug.Assert(typeof(PdfDictionary).IsAssignableFrom(type));
+
+ ConstructorInfo? ctorInfo;
+ PdfDictionary? dict;
+ if (oldDictionary == null)
+ {
+ // Try constructor with signature 'Ctor(PdfDocument owner)'.
+ ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, [typeof(PdfDocument)], null);
+
+ if (ctorInfo != null)
+ {
+ dict = ctorInfo.Invoke([OwningContainer.Owner]) as PdfDictionary;
+ }
+ else
+ {
+ // Try constructor with signature 'Ctor()'.
+ ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, [], null);
+
+ if (ctorInfo == null)
+ throw new InvalidOperationException($"No appropriate constructor found for type {type.FullName}.");
+ dict = ctorInfo.Invoke([]) as PdfDictionary;
+ }
+ Debug.Assert(dict?.IsTransformed is false);
+ dict.SetTransformed();
+ }
+ else
+ {
+ // Use constructor with signature 'Ctor(PdfDictionary dict)'.
+ ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, [typeof(PdfDictionary)], null);
+ if (ctorInfo == null)
+ throw new InvalidOperationException($"No appropriate constructor found for type {type.FullName}.");
+
+ dict = ctorInfo.Invoke([oldDictionary]) as PdfDictionary;
+ //Debug.Assert(dict?.IsTransformed is false);
+ if (dict?.IsTransformed == false)
+ {
+ Debugger.Break();
+ dict?.SetTransformed();
+ }
+ }
+ Debug.Assert(dict?.IsTransformed is true);
+ return dict ?? NRT.ThrowOnNull();
+ }
+#endif
+ ///
+ /// Ensures that the specified value type is at least
+ /// of the specified generic type.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected internal void EnsureValueType(Type valueType)
+ {
+
+ if (typeof(T).IsAssignableFrom(valueType))
+ return;
+ ThrowInappropriateValueType(typeof(T), valueType);
+ }
+
+ void ThrowInappropriateValueType(Type required, Type specified)
+ {
+ throw new InvalidOperationException($"The specified value type '{specified.FullName}' is not derived from '{required.FullName}'.");
+ }
+
+ ///
+ /// Must not use direct primitive objects.
+ /// Use e.g. PdfString instead of PdfStringObject.
+ ///
+ protected static void FailForDirectPrimitiveObject(PdfObject obj)
+ {
+ // Case: Direct primitive object
+ // Use e.g. PdfString instead of PdfStringObject.
+
+ Debug.Assert(obj is PdfPrimitiveObject);
+
+ var name = obj.GetType().Name;
+ var replaceType = "(unknown)";
+ if (!name.EndsWith("Object", StringComparison.Ordinal))
+ throw new InvalidOperationException("Use Array, Dictionary, or Simple type.");
+ replaceType = name[..^"Object".Length];
+ var message = $"{name} can only be used as an indirect object. Use {replaceType} instead.";
+ throw new InvalidOperationException(message);
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/EntryInfoAttribute.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/EntryInfoAttribute.cs
index 78dcf9b6..391ace27 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/EntryInfoAttribute.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/EntryInfoAttribute.cs
@@ -6,44 +6,54 @@
namespace PdfSharp.Pdf
{
///
- /// Specifies the type of a key’s value in a dictionary.
+ /// Specifies the type of the key’s value in a dictionary.
///
[Flags]
enum KeyType
{
- Name = 0x00000001,
- String = 0x00000002,
- Boolean = 0x00000003,
- Integer = 0x00000004,
- Real = 0x00000005,
- Date = 0x00000006,
- Rectangle = 0x00000007,
- Array = 0x00000008,
- Dictionary = 0x00000009,
- Stream = 0x0000000A,
- NumberTree = 0x0000000B,
- Function = 0x0000000C,
- TextString = 0x0000000D,
- ByteString = 0x0000000E,
- NameTree = 0x0000000F,
- FileSpecification = 0x00000010,
+ // @@@ Review Flags
+ Name = 0x000_00001,
+ String = 0x0000_0002,
+ Boolean = 0x0000_0003,
+ Integer = 0x0000_0004,
+ Real = 0x0000_0005,
+ Date = 0x0000_0006,
+ Rectangle = 0x0000_0007, // IMPROVE: See #US291
+ Array = 0x0000_0008,
+ Dictionary = 0x0000_0009,
+ Stream = 0x0000_000A,
+ NumberTree = 0x0000_000B,
+ Function = 0x0000_000C,
+ TextString = 0x0000_000D,
+ ByteString = 0x0000_000E,
+ NameTree = 0x0000_000F,
+ FileSpecification = 0x0000_0010,
- NameOrArray = 0x00000100,
- NameOrDictionary = 0x00000200,
- ArrayOrDictionary = 0x00000300,
- StreamOrArray = 0x00000400,
- StreamOrName = 0x00000500,
- ArrayOrNameOrString = 0x00000600,
- FunctionOrName = 0x000000700,
- Various = 0x000000800,
+ NameOrArray = 0x0000_0100,
+ NameOrDictionary = 0x0000_0200,
+ ArrayOrDictionary = 0x0000_0300,
+ StreamOrArray = 0x0000_0400,
+ StreamOrName = 0x0000_0500,
+ StreamOrDictionary = 0x0000_0600,
+ ArrayOrNameOrString = 0x0000_0700,
+ FunctionOrName = 0x0000_0800,
+ Various = 0x00000_0900,
+ ArrayOfDictionaries = 0x00000_0A00,
+ NameOrByteStringOrArray = 0x0000_0B00, // #US373: TODO Check String, ByteString, TextString - check if we have duplicates.
+ StringOrDictionary = 0x0000_0C00,
+ TextStringOrStream = 0x0000_0D00,
+ TextStringOrTextStream = 0x0000_0E00,
+ BooleanOrDictionary = 0x00000_0F00,
- TypeMask = 0x00000FFF,
+ TypeMask = 0x0000_0FFF,
- Optional = 0x00001000,
- Required = 0x00002000,
- Inheritable = 0x00004000,
- MustBeIndirect = 0x00010000,
- MustNotBeIndirect = 0x00020000,
+ Optional = 0x0000_1000,
+ Required = 0x0000_2000,
+ Inheritable = 0x0000_4000,
+ MustBeIndirect = 0x0001_0000,
+ MustNotBeIndirect = 0x0002_0000,
+
+ DeprecatedIn20 = 0x000F_0000
}
///
@@ -100,9 +110,10 @@ public KeyType KeyType
KeyType _entryType;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- public Type ObjectType
+ // ReSharper disable once ConvertToAutoProperty
+ public Type? ObjectType
{
- get => _objectType!; // ?? NRT.ThrowOnNull(); Can be null.
+ get => _objectType!;
set => _objectType = value;
}
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
@@ -110,7 +121,7 @@ public Type ObjectType
public string FixedValue
{
- get => _fixedValue!; // ?? NRT.ThrowOnNull(); Can be null.
+ get => _fixedValue!;
set => _fixedValue = value;
}
string? _fixedValue;
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs
index 1c38d1ed..a9c62141 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs
@@ -1,9 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System.Diagnostics.CodeAnalysis;
using System.Reflection;
-using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Attachments;
namespace PdfSharp.Pdf
{
@@ -24,6 +23,10 @@ public KeyDescriptor(KeyInfoAttribute attribute)
FixedValue = attribute.FixedValue;
ObjectType = attribute.ObjectType;
+ if (ObjectType != null!
+ && !typeof(PdfArray).IsAssignableFrom(ObjectType) && !typeof(PdfDictionary).IsAssignableFrom(ObjectType))
+ throw new InvalidOperationException($"The ObjectType '{ObjectType.FullName}' must be derived from PdfArray or PdfDictionary.");
+
if (Version == "")
Version = "1.0";
}
@@ -35,12 +38,12 @@ public KeyDescriptor(KeyInfoAttribute attribute)
public KeyType KeyType { get; set; }
- public string KeyValue { get; set; } = default!;
+ public string KeyValue { get; set; } = "";
public string FixedValue { get; }
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- public Type ObjectType { get; set; }
+ public Type? ObjectType { get; set; }
public bool CanBeIndirect => (KeyType & KeyType.MustNotBeIndirect) == 0;
@@ -48,7 +51,7 @@ public KeyDescriptor(KeyInfoAttribute attribute)
/// Returns the type of the object to be created as value for the described key.
///
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- public Type GetValueType()
+ public Type? GetValueType()
{
var type = ObjectType;
if (type == null!)
@@ -61,6 +64,7 @@ public Type GetValueType()
break;
case KeyType.String:
+ case KeyType.TextString: // #US373
type = typeof(PdfString);
break;
@@ -96,6 +100,10 @@ public Type GetValueType()
type = typeof(PdfDictionary);
break;
+ case KeyType.StreamOrDictionary:
+ type = typeof(PdfDictionary);
+ break;
+
case KeyType.NumberTree:
type = typeof(PdfNumberTreeNode);
break;
@@ -107,7 +115,18 @@ public Type GetValueType()
case KeyType.FileSpecification:
type = typeof(PdfFileSpecification);
break;
-
+#if true
+ case KeyType.NameOrArray:
+ case KeyType.NameOrDictionary: // #US373
+ case KeyType.ArrayOrDictionary:
+ case KeyType.StreamOrArray:
+ case KeyType.ArrayOrNameOrString:
+ case KeyType.NameOrByteStringOrArray:
+ case KeyType.StringOrDictionary:
+ case KeyType.TextStringOrStream:
+ case KeyType.TextStringOrTextStream:
+ return null!;
+#else
// The following types are not yet used.
case KeyType.NameOrArray:
@@ -121,8 +140,8 @@ public Type GetValueType()
case KeyType.ArrayOrNameOrString:
return null!; // HACK_OLD: Make PdfOutline work
- //throw new NotImplementedException("KeyType.ArrayOrNameOrString");
-
+ //throw new NotImplementedException("KeyType.ArrayOrNameOrString");
+#endif
default:
Debug.Assert(false, "Invalid KeyType: " + KeyType);
break;
@@ -137,7 +156,9 @@ public Type GetValueType()
///
class DictionaryMeta
{
- public DictionaryMeta([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type)
+ public DictionaryMeta(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
+ Type type)
{
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
foreach (var field in fields)
@@ -148,7 +169,7 @@ public DictionaryMeta([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes
var attribute = (KeyInfoAttribute)attributes[0];
var descriptor = new KeyDescriptor(attribute)
{
- KeyValue = (string)field.GetValue(null)!
+ KeyValue = (string)(field.GetValue(null) ?? throw new InvalidOperationException("Key field has no key name value."))
};
_keyDescriptors[descriptor.KeyValue] = descriptor;
}
@@ -162,7 +183,9 @@ public DictionaryMeta([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes
/// The type.
/// Default type of the content key.
/// Default type of the content.
- public DictionaryMeta([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type,
+ public DictionaryMeta(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
+ Type type,
KeyType defaultContentKeyType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type defaultContentType) : this(type)
@@ -181,7 +204,7 @@ public KeyDescriptor? this[string key]
return keyDescriptor ?? _defaultContentKeyDescriptor;
}
}
- readonly Dictionary _keyDescriptors = new();
+ readonly Dictionary _keyDescriptors = [];
///
/// The default content key descriptor used if no descriptor exists for a given key.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/Name.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/Name.cs
new file mode 100644
index 00000000..b120910e
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/Name.cs
@@ -0,0 +1,456 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using System.Text;
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
+using PdfSharp.Pdf.Internal;
+
+// v7.0 Ready
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf
+{
+ ///
+ /// Implements a PDF name as a value type.
+ ///
+ public readonly struct Name : IComparer, IEquatable
+ {
+ // Reference 2.0: 7.3.5 Name objects / Page 27
+
+ // ReSharper disable GrammarMistakeInComment
+
+ // “Literal name” and “resulting name” are defined in PDF specs.
+ // See Table 4 — Examples of literal names on page 28.
+ //
+ // Canonical name is what we use during programming
+ //
+ // Literal name | Resulting name | << Terms used by Adobe/ISO
+ // Literal name | (not used) | Canonical name << Terms used in PDFsharp
+ // ----------------+------------------+------------------
+ // /Name1 | Name1 | /Name1
+ // /Lime#20Green | Lime Green | /Lime Green
+ // /Mambo#20#235 | Mambo #5 | /Mambo #5
+
+ // In one sentence:
+ // In PDFsharp, the canonical name is the resulting name prefixed with a '/'.
+ // This is compatible with the previous implementation of PdfName.
+
+ // ReSharper restore GrammarMistakeInComment
+
+ // Literal: What’s written into or coming from a PDF file.
+ // Canonical: What’s in the .NET string and used in C# code.
+ // Regular: Literal and canonical name are identical because
+ // there are characters that are escaped.
+ //
+ // The term resulting name used by PDF specification is identical with
+ // the canonical name, except the resulting name has no slash but the
+ // canonical name has one.
+
+ // What happens if a '\0' character appears in a canonical name?
+ // PDFsharp skips this character.
+ // What happens if a "#00", "#0", or "#xx" character appears in a literal name?
+ // PDFsharp skips this character.
+
+ ///
+ /// Initializes a new instance of the struct
+ /// with the empty name "/".
+ ///
+ public Name()
+ {
+ _literalName = _canonicalName = "/";
+ }
+
+ ///
+ /// Initializes a new instance of the struct
+ /// with a canonical name. The string must start with a "/".
+ ///
+ public Name(string canonicalName)
+ {
+ if (String.IsNullOrEmpty(canonicalName))
+ throw new ArgumentException("A PDF name must not be null or empty.", nameof(canonicalName));
+
+ if (canonicalName[0] != '/')
+ throw new ArgumentException("A PDF name must start with a '/'.");
+
+ _literalName = BuildLiteralName(canonicalName);
+ _canonicalName = canonicalName;
+ }
+
+ internal Name(string literalName, string canonicalName)
+ {
+ _literalName = literalName;
+ _canonicalName = canonicalName;
+ }
+
+ ///
+ /// Creates a object from an enum value.
+ ///
+ public static Name FromEnum(T value) where T : Enum
+ => new('/' + value.ToString());
+
+ ///
+ /// Gets the literal value of the name.
+ /// That is the form which is written in a PDF file.
+ ///
+ // ReSharper disable once ConvertToAutoPropertyWhenPossible
+ public string LiteralValue => _literalName;
+
+ ///
+ /// Gets the canonical value of the name.
+ /// That is the form used as keys in PdfDictionary.
+ ///
+ // ReSharper disable once ConvertToAutoPropertyWhenPossible
+ public string Value => _canonicalName;
+
+ public bool IsEmpty => _canonicalName.Length == 1;
+
+ ///
+ /// Ensures that the name is formally correct.
+ /// It must be a non-empty string starting with a '/'.
+ ///
+ public static void EnsureName(string name)
+ {
+ if (String.IsNullOrEmpty(name))
+ throw new ArgumentNullException(nameof(name));
+
+ if (name[0] != '/')
+ throw new ArgumentException($"The PDF name '{name}' must start with a slash ('/').");
+ }
+
+ ///
+ /// Converts a string into a canonical name by adding a '/'
+ /// as prefix to the string.
+ /// If the specified string already starts with a '/'
+ /// no action is taken.
+ ///
+ public static string MakeName(string name)
+ {
+ if (String.IsNullOrEmpty(name))
+ return "/";
+ if (name[0] != '/')
+ return String.Concat('/', name);
+ return name;
+ }
+
+ ///
+ /// Removes the slash from a string, that is needed at the beginning of a PDF name.
+ ///
+ public static string RemoveSlash(string value)
+ {
+ return value.Length == 0 || value[0] != '/'
+ ? value
+ : value[1..];
+ }
+ ///
+ /// Creates a new instance of Name from a literal name.
+ ///
+ public static Name FromLiteralName(string literalName)
+ {
+ if (String.IsNullOrEmpty(literalName))
+ throw new ArgumentNullException(nameof(literalName));
+
+ if (literalName[0] != '/')
+ throw new ArgumentException($"Literal name '{literalName}' must start with a slash ('/').");
+
+ var (literal, canonical) = BuildCanonicalName(literalName);
+ return new(literal, canonical);
+ }
+
+ ///
+ /// Creates a new instance of Name from a canonical name.
+ ///
+ public static Name FromCanonicalName(string canonicalName)
+ {
+ if (String.IsNullOrEmpty(canonicalName))
+ throw new ArgumentNullException(nameof(canonicalName));
+
+ if (canonicalName[0] != '/')
+ throw new ArgumentException($"Canonical name '{canonicalName}' must start with a slash ('/').");
+
+ return new(BuildLiteralName(canonicalName), canonicalName);
+ }
+
+ public static readonly Name Empty = new();
+
+ static string BuildLiteralName(string canonicalName)
+ {
+ // Case: The canonical name is defined and the literal name is created.
+ // ISSUE: What if we get a '\0'? -> ignore it
+
+
+ Debug.Assert(!String.IsNullOrEmpty(canonicalName) && canonicalName[0] == '/');
+
+ // The specification says:
+ // “Regular characters that are outside the range EXCLAMATION MARK(21h) (!) to
+ // TILDE (7Eh) (~) should be written using the hexadecimal notation.”
+ // and
+ // “
+ // a) A NUMBER SIGN (23h) (#) in a name shall be written by using its 2-digit hexadecimal
+ // code (23), preceded by the NUMBER SIGN.
+ // b) Any character in a name that is a regular character (other than NUMBER SIGN) shall
+ // be written as itself or by using its 2-digit hexadecimal code, preceded by the
+ // NUMBER SIGN.
+ // c) Any character that is not a regular character shall be written using its 2-digit
+ // hexadecimal code, preceded by the NUMBER SIGN only.
+ // ”
+ // PDFsharp escapes the 10 regular characters that are used as delimiters defined in
+ // “Table 2 — Delimiter characters” on page 23.
+
+ // Step 1
+ // Encode to raw string using UTF-8 encoding if any char is larger than 126.
+ // 127 [DEL] is not a valid value and is also encoded.
+ string name = canonicalName;
+ int length = name.Length;
+ for (int idx = 1; idx < length; idx++)
+ {
+ char ch = name[idx];
+ if (ch == '\0')
+ {
+ // PDFsharp skips null characters.
+ idx++;
+ continue;
+ }
+ if (ch >= 127)
+ {
+ // Case: First non-ASCII character found - convert whole string to raw UTF-8.
+
+ // Interpret name as Unicode and get the bytes.
+ var bytes = Encoding.UTF8.GetBytes(name);
+ // Convert bytes into a PDFsharp raw string.
+ name = PdfEncoders.RawEncoding.GetString(bytes);
+ break;
+ }
+ }
+
+ // Step 2
+ // Escape characters/bytes not allowed in PDF names.
+ // Avoid allocation of StringBuilder if name is regular.
+ StringBuilder? sb = null;
+ length = name.Length;
+ for (int idx = 1; idx < length; idx++)
+ {
+ var ch = name[idx];
+ Debug.Assert(ch < 256);
+ // Must escape this character/byte?
+ if (ch switch
+ {
+ < '!' or > '~' => true,
+ '#' => true, // PDF name escaping
+ // Reference 2.0: 7.1 Table 2 — Delimiter characters / Page 23
+ '(' => true, // PDF string delimiter
+ ')' => true, // "
+ '<' => true, // PDF dictionary delimiter
+ '>' => true, // "
+ '[' => true, // PDF array delimiter
+ ']' => true, // "
+ '{' => true, // Type 4 PostScript calculator functions
+ '}' => true, // "
+ '/' => true, // PDF names delimiter
+ '%' => true, // PDF comments delimiter
+ _ => false
+ })
+ {
+ sb ??= new(name[..idx]);
+ sb.Append('#');
+ byte hi = (byte)((ch >> 4) + '0');
+ byte lo = (byte)((ch & 0xF) + '0');
+ sb.Append((char)(hi < ':' ? hi : hi + ('A' - ':')));
+ sb.Append((char)(lo < ':' ? lo : lo + ('A' - ':')));
+ }
+ else
+ {
+ // No string builder means no escaping needed yet.
+ sb?.Append(ch);
+ }
+ }
+ // String builder is null if name is regular.
+ return sb?.ToString() ?? name;
+ }
+
+ static (string Literal, string Canonical) BuildCanonicalName(string literalName)
+ {
+ // ISSUE: What if we get a '#00'? Solved: ignore.
+
+ // Case: The literal name is defined and the canonical name is created.
+
+ Debug.Assert(true); // Dummy statement to prevent ill formatted code because of label.
+ TryAgain:
+ string name = literalName;
+ // Step 1
+ // Unescape all characters/bytes.
+ // Avoid allocation of StringBuilder if name is regular.
+ StringBuilder? sb = null;
+ int length = name.Length;
+ bool illegalLiteralCharacterFound = false;
+ for (int idx = 1; idx < length; idx++)
+ {
+ var ch = name[idx];
+ if (ch == '#')
+ {
+ sb ??= new(name[..idx]);
+
+ // Check the bs stuff.
+ if (idx + 2 >= length)
+ {
+ // Just add some filler and don’t care.
+ literalName += idx + 1 == length ? "00" : '0';
+ goto TryAgain;
+ }
+
+ char hi = name[++idx];
+ char lo = name[++idx];
+ ch = (char)((hi switch
+ {
+ >= '0' and <= '9' => hi - '0',
+ >= 'A' and <= 'F' => hi - ('A' - 10), // Without parenthesis the expressions are not
+ >= 'a' and <= 'f' => hi - ('a' - 10), // optimized as constant expressions in IL.
+ _ => LogError(hi)
+ } << 4) + lo switch
+ {
+ >= '0' and <= '9' => lo - '0',
+ >= 'A' and <= 'F' => lo - ('A' - 10),
+ >= 'a' and <= 'f' => lo - ('a' - 10),
+ _ => LogError(lo)
+ });
+ if (ch == '\0')
+ {
+ PdfSharpLogHost.Logger.LogError("Name '{name}' contains illegal '#' sequence that evaluates to '\\0' and is skipped.", name);
+ illegalLiteralCharacterFound = true;
+ continue;
+ }
+ static int LogError(int ch)
+ {
+ PdfSharpLogHost.Logger.LogError("Name contains illegal character '{char}' in escape sequence.", ch);
+ return 0;
+ }
+ sb.Append(ch);
+ }
+ else
+ {
+ // What if a literal name contains illegal characters?
+ if (ch switch
+ {
+ < '!' or > '~' => true,
+ // Already checked: '#' => true, // PDF name escaping
+ // Reference 2.0: 7.1 Table 2 — Delimiter characters / Page 23
+ '(' => true, // PDF string delimiter
+ ')' => true, // "
+ '<' => true, // PDF dictionary delimiter
+ '>' => true, // "
+ '[' => true, // PDF array delimiter
+ ']' => true, // "
+ '{' => true, // Type 4 PostScript calculator functions
+ '}' => true, // "
+ '/' => true, // PDF names delimiter
+ '%' => true, // PDF comments delimiter
+ _ => false
+ })
+ {
+ // The literal name contains at least one illegal character.
+ illegalLiteralCharacterFound = true;
+ sb ??= new(name[..idx]);
+ // Append the UTF-8 bytes of the illegal character as a raw string.
+ sb.Append(PdfEncoders.RawEncoding.GetString(Encoding.UTF8.GetBytes([ch])));
+ }
+ else
+ {
+ sb?.Append(ch);
+ }
+ }
+ }
+
+ // Step 2
+ // If a StringBuilder was created, at least one either escaped or an illegal character was
+ // found. As the PDF specs recommend, we assume that the string is UTF-8 encoded.
+ // If no string builder was created, name is regular and therefore always legal.
+ if (sb != null)
+ {
+ // Convert raw name into a Unicode string using UTF-8 decoding. This is our canonical name.
+ name = Encoding.UTF8.GetString(PdfEncoders.RawEncoding.GetBytes(sb.ToString()));
+ }
+
+ if (illegalLiteralCharacterFound)
+ {
+ // Rebuild literal name because it was not a legal literal one.
+ literalName = BuildLiteralName(name);
+ }
+ return (literalName, name);
+ }
+
+ public bool Equals(Name other)
+ => Comparer.Compare(this, other) == 0;
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is Name name)
+ return Comparer.Compare(this, name) == 0;
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (_literalName.GetHashCode() * 397) ^ _canonicalName.GetHashCode();
+ }
+ }
+
+ public static implicit operator Name(string name) => new(name);
+ public static implicit operator String(Name name) => name.Value;
+
+ public static bool operator ==(Name l, Name r)
+ => Comparer.Compare(l, r) == 0;
+
+ public static bool operator !=(Name l, Name r)
+ => !(l == r);
+
+ ///
+ /// Compares two names.
+ ///
+ public int Compare(Name l, Name r)
+ => Comparer.Compare(l, r);
+
+ ///
+ /// Gets the comparer for this type.
+ ///
+ public static NameComparer Comparer => _comparer ??= new();
+
+ ///
+ /// Implements a comparer that compares Name objects.
+ ///
+ public class NameComparer : IComparer
+ {
+ ///
+ /// Compares two names and returns a value indicating whether one is less than, equal to, or greater than the other.
+ ///
+ public int Compare(Name? l, Name? r)
+ {
+ // Compare the canonical name.
+ if (l != null)
+ {
+ return r != null
+ ? String.Compare(l.Value.Value, r.Value.Value, StringComparison.Ordinal)
+ : 1;
+ }
+ return r == null ? 0 : -1;
+ }
+ }
+
+ // Recall that Name is a value type. Therefore, it makes no sense to
+ // lazy evaluate one of the two strings when the other was specified.
+
+ ///
+ /// The literal name with escaped characters.
+ ///
+ readonly string _literalName;
+
+ ///
+ /// The resulting name with solidus.
+ ///
+ readonly string _canonicalName;
+
+ static NameComparer? _comparer;
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ParentInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ParentInfo.cs
new file mode 100644
index 00000000..e7942667
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ParentInfo.cs
@@ -0,0 +1,82 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf
+{
+ ///
+ /// Represents a reference to a PdfDictionary.DictionaryElements or PdfArray.ArrayElements
+ /// a direct PdfObject belongs to. Only a direct PdfObject can have a structure parent.
+ ///
+ internal class ParentInfo
+ {
+ ///
+ /// Creates a new ParentInfo instance for a PdfObject that is owned by a PdfDictionary.
+ ///
+ /// The dictionary elements that owns the owned object belongs to.
+ /// The key in the owning dictionary.
+ public ParentInfo(PdfDictionary.DictionaryElements elements, string key)
+ {
+ OwningElements = elements;
+ Key = key;
+ }
+
+ ///
+ /// Creates a new ParentInfo instance for a PdfObject that is owned by a PdfArray.
+ ///
+ /// The array elements that owns the owned object belongs to.
+ /// The index in the owning array.
+ public ParentInfo(PdfArray.ArrayElements elements, int index)
+ {
+ OwningElements = elements;
+ Index = index;
+ }
+
+ ///
+ /// Returns true if this ParentInfo belongs to a PdfArray, false otherwise.
+ ///
+ public bool IsArray => Index != -1;
+
+ ///
+ /// Returns true if this ParentInfo belongs to a PdfDictionary, false otherwise.
+ ///
+ public bool IsDictionary => Key.Length > 0;
+
+ ///
+ /// Gets the index of the PdfObject in the PDF array if it is owned by an array,
+ /// -1 otherwise.
+ ///
+ public int Index { get; private set; } = -1;
+
+ ///
+ /// Gets the of the PdfObject in the PDF dictionary if it is owned by a dictionary,
+ /// empty string otherwise.
+ ///
+ public string Key { get; } = "";
+
+ public ElementsBase OwningElements { get; private set; }
+
+ ///
+ /// Gets the owning PdfArray if the PdfObject is owned by an array,
+ /// null otherwise.
+ ///
+ public PdfArray OwningArray => IsArray ? (PdfArray)OwningElements.OwningContainer : null!;
+
+ ///
+ /// Gets the owning PdfDictionary if the PdfObject is owned by a dictionary,
+ /// null otherwise.
+ ///
+ public PdfDictionary OwningDictionary => IsDictionary ? (PdfDictionary)OwningElements.OwningContainer : null!;
+
+ ///
+ /// Adjust the index if the owning array is modified.
+ /// This happens when an item is inserted or deleted from an array before this index.
+ ///
+ /// The correction value added to the current index.
+ internal void AdjustIndex(int offset)
+ {
+ Debug.Assert(IsArray);
+ Index += offset;
+ Debug.Assert(Index >= 0 && Index < ((PdfArray.ArrayElements)OwningElements).Count);
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs
index 273e9f2f..5c4d3a95 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs
@@ -1,32 +1,47 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using System.Collections;
+using System.Runtime.CompilerServices;
using System.Text;
+using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
+using PdfSharp.Logging;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.IO;
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
namespace PdfSharp.Pdf
{
///
/// Represents a PDF array object.
///
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
- public class PdfArray : PdfObject, IEnumerable
+ public class PdfArray : PdfContainer, IEnumerable
{
+ // Reference 1.7: 3.2.5 Array Objects / Page 58
+ // Reference 2.0: 7.3.6 Array objects / Page 29
+
///
/// Initializes a new instance of the class.
///
public PdfArray()
- { }
+ {
+ // Direct object.
+ ItemFlags |= ItemFlags.IsArray;
+ }
///
/// Initializes a new instance of the class.
///
/// The document.
- public PdfArray(PdfDocument document)
- : base(document)
- { }
+ /// If true creates an indirect object.
+ public PdfArray(PdfDocument document, bool createIndirect = false)
+ : base(document, createIndirect)
+ {
+ ItemFlags |= ItemFlags.IsArray;
+ }
///
/// Initializes a new instance of the class.
@@ -38,21 +53,62 @@ public PdfArray(PdfDocument document, params PdfItem[] items)
{
foreach (var item in items)
Elements.Add(item);
+
+ ItemFlags |= ItemFlags.IsArray;
+ }
+
+ public PdfArray(params PdfItem[] items) : this()
+ {
+ foreach (var item in items)
+ Elements.Add(item);
+ }
+
+ internal PdfArray(params int[] items) : this()
+ {
+ foreach (var item in items)
+ Elements.Add(new PdfInteger(item));
+ }
+
+ internal PdfArray(params double[] items) : this()
+ {
+ foreach (var item in items)
+ Elements.Add(new PdfReal(item));
}
///
- /// Initializes a new instance from an existing dictionary. Used for object type transformation.
+ /// Initializes a new instance from an existing array.
+ /// Used for object type transformation.
///
- /// The array.
protected PdfArray(PdfArray array)
: base(array)
{
- if (array._elements != null)
- array._elements.ChangeOwner(this);
+#if DEBUG
+ // Protect against unintended invocation of this cont.
+ var oldType = array.GetType();
+ if (oldType != typeof(PdfArray))
+ {
+ var newType = GetType();
+ if (oldType == newType)
+ {
+ throw new InvalidOperationException($"You try to convert a PDF array into type '{newType.FullName}', " +
+ $"but the array is already of this type.");
+ }
+
+ if (!oldType.IsAssignableFrom(newType))
+ {
+ throw new InvalidOperationException($"You try to convert type '{oldType.FullName}' into type '{newType.FullName}', " +
+ $"but '{newType.Name}' is not derived from '{oldType.Name}'.");
+ }
+ }
+#endif
+ // Move ownership of elements to this instance.
+ array._elements?.ChangeOwner(this);
+ array.SetDead();
+ ItemFlags |= ItemFlags.IsArray | ItemFlags.IsTransformed;
}
///
- /// Creates a copy of this array. Direct elements are deep copied.
+ /// Creates a copy of this array. Direct elements are deep-copied.
/// Indirect references are not modified.
///
public new PdfArray Clone()
@@ -63,25 +119,34 @@ protected PdfArray(PdfArray array)
///
protected override object Copy()
{
+ // Clone array.
var array = (PdfArray)base.Copy();
- if (array._elements != null)
+ var elements = array._elements;
+ if (elements != null)
{
- array._elements = array._elements.Clone();
- int count = array._elements.Count;
- for (int idx = 0; idx < count; idx++)
- {
- PdfItem item = array._elements[idx];
- if (item is PdfObject)
- array._elements[idx] = item.Clone();
- }
+ elements = elements.Clone();
+ array._elements = elements;
+ elements.ChangeOwner(array);
}
return array;
}
///
- /// Gets the collection containing the elements of this object.
+ /// Gets the collection containing the elements of this array.
+ ///
+ public ArrayElements Elements
+ {
+ get
+ {
+ EnsureAlive();
+ return _elements ??= new(this);
+ }
+ }
+
+ ///
+ /// The elements of the array.
///
- public ArrayElements Elements => _elements ??= new ArrayElements(this);
+ ArrayElements? _elements;
///
/// Returns an enumerator that iterates through a collection.
@@ -93,48 +158,126 @@ IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
///
- /// Returns a string with the content of this object in a readable form. Useful for debugging purposes only.
+ /// Returns a string with the content of this object in a readable form.
+ /// Useful for debugging purposes only.
///
public override string ToString()
{
- var pdf = new StringBuilder();
- pdf.Append("[ ");
+ var text = new StringBuilder();
+ text.Append('[');
int count = Elements.Count;
for (int idx = 0; idx < count; idx++)
- pdf.Append(Elements[idx] + " ");
- pdf.Append("]");
- return pdf.ToString();
+ {
+ if (idx != 0)
+ text.Append(' ');
+ text.Append(Elements[idx]);
+ }
+ text.Append(']');
+ return text.ToString();
}
internal override void WriteObject(PdfWriter writer)
{
writer.WriteBeginObject(this);
int count = Elements.Count;
+#if TEST_CODE_
+ // Ensure that PDFsharp does not use PdfLiterals anymore (PdfMatrix, PdfOutline, ...).
+ //if (count == 4 && Elements.GetName(1) == "/XYZ" && Elements.GetInteger(2) == 842)
+ if (count is > 3 and <= 5)
+ {
+ if (ParentInfo?.OwningElements.OwningContainer.ObjectID.ObjectNumber == 9658)
+ Debugger.Break();
+ var item0 = Elements[0];
+ var item1 = Elements[1];
+ var item2 = Elements[2];
+ if (item0 is PdfReference iref && iref.ObjectNumber == 29)
+ {
+ _ = typeof(int);
+ }
+ if (item1 is PdfName name && name.Value == "/XYZ")
+ // &&
+ //(item2 is PdfInteger integer && integer.Value == 842))
+ {
+ _ = typeof(int);
+ }
+ }
+#endif
for (int idx = 0; idx < count; idx++)
{
- PdfItem value = Elements[idx];
+ var value = Elements[idx];
value.WriteObject(writer);
}
writer.WriteEndObject();
}
///
- /// Represents the elements of an PdfArray.
+ /// Clones the elements of the specified PDF dictionary.
+ ///
+ internal void CloneElementsOf(PdfArray array)
+ {
+ _elements = array.Elements.Clone();
+ // Note that ParentInfo is still null for each item.
+ _elements.OwningContainer = this;
+ }
+
+ // There is no "ArrayMetadata".
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ new void EnsureAlive()
+ {
+ if (IsDead)
+ {
+ throw new InvalidOperationException(
+ "This array cannot be used anymore, because its content was now owned by an object of a derived class.");
+ }
+ }
+
+ ///
+ /// Represents the elements of a PdfArray.
///
- public sealed class ArrayElements : IList, ICloneable
+ [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
+ public sealed class ArrayElements : ElementsBase, IList, ICloneable
{
- internal ArrayElements(PdfArray array)
+ internal ArrayElements(PdfArray owningArray)
+ : base(owningArray)
{
- _elements = new List();
- _ownerArray = array;
+ Debug.Assert(ReferenceEquals(OwningArray, owningArray));
}
object ICloneable.Clone()
{
- var elements = (ArrayElements)MemberwiseClone();
- elements._elements = [..elements._elements];
- elements._ownerArray = null;
- return elements;
+ // Shallow clone the list elements.
+ var arrayElements = (ArrayElements)MemberwiseClone();
+ var elements = new List(arrayElements._elements);
+ arrayElements._elements = elements;
+ arrayElements.OwningContainer = null!;
+
+ // Clone all direct objects.
+ int count = Count;
+ for (int idx = 0; idx < count; idx++)
+ {
+ var item = elements[idx];
+ if (item is PdfObject obj)
+ {
+ // Case: item is a direct object.
+
+ Debug.Assert(obj.IsIndirect is false);
+ Debug.Assert(obj.ParentInfo is not null);
+ obj = obj.Clone();
+ Debug.Assert(obj.ParentInfo is null);
+ obj.SetStructureParent(arrayElements, idx);
+ elements[idx] = obj;
+ }
+ else if (item is PdfReference reference)
+ {
+ reference.AddRef();
+ }
+ else
+ {
+ _ = typeof(int);
+ }
+ }
+ return arrayElements;
}
///
@@ -146,221 +289,1208 @@ public ArrayElements Clone()
///
/// Moves this instance to another array during object type transformation.
///
- internal void ChangeOwner(PdfArray array)
+ internal void ChangeOwner(PdfArray newOwningArray)
{
- if (_ownerArray != null)
+ if (OwningArray != null!)
{
- // ???
+ // Can this assertion really fail?
+ Debug.Assert(ReferenceEquals(this, OwningArray._elements));
+
+ // Disconnect old owner from this ArrayElements.
+ OwningArray._elements = null;
}
// Set new owner.
- _ownerArray = array;
+ OwningContainer = newOwningArray;
// Set owners elements to this.
- array._elements = this;
+ newOwningArray._elements = this;
}
+ // ===== PdfBoolean =====
+
///
- /// Converts the specified value to boolean.
- /// If the value does not exist, the function returns false.
- /// If the value is not convertible, the function throws an InvalidCastException.
- /// If the index is out of range, the function throws an ArgumentOutOfRangeException.
+ /// Gets the boolean value for the specified index.
+ /// If the value exists but is neither a boolean nor a PDF reference to a boolean,
+ /// the function throws an InvalidOperationException.
///
public bool GetBoolean(int index)
{
- if (index < 0 || index >= Count)
- throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3);
+ TryGetBooleanInternal(index, out bool result, true);
+ return result;
+ }
+
+ ///
+ /// Tries to get the boolean value for the specified index.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetBoolean(int index, out bool result)
+ => TryGetBooleanInternal(index, out result, false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetBooleanInternal(int index, out bool result, bool throwOnTypeMismatch)
+ {
+ EnsureIndex(index, true);
- object obj = this[index];
- //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21
- return obj switch
+ var value = this[index];
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- null => false,
PdfBoolean boolean => boolean.Value,
- PdfBooleanObject booleanObject => booleanObject.Value,
- _ => throw new InvalidCastException("GetBoolean: Object is not a boolean.")
+ PdfBooleanObject boolean => boolean.Value,
+ _ => Fail()
};
+ return success;
+
+ bool Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfBoolean), value.GetType()).Message);
+ }
+ success = false;
+ return false;
+ }
+ }
+
+ ///
+ /// Sets the entry to the specified value.
+ ///
+ public void SetBoolean(int index, bool value)
+ {
+ SetValueInternal(index, new PdfBoolean(value));
}
+ // ===== PdfInteger =====
+
///
- /// Converts the specified value to integer.
- /// If the value does not exist, the function returns 0.
- /// If the value is not convertible, the function throws an InvalidCastException.
- /// If the index is out of range, the function throws an ArgumentOutOfRangeException.
+ /// Gets the integer value for the specified index.
+ /// If the value exists but is neither an integer nor a PDF reference to an integer,
+ /// the function throws an InvalidOperationException.
///
public int GetInteger(int index)
{
- if (index < 0 || index >= Count)
- throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3);
+ TryGetIntegerInternal(index, out int result, true);
+ return result;
+ }
+
+ ///
+ /// Tries to get the integer value for the specified index.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetInteger(int index, out int result)
+ => TryGetIntegerInternal(index, out result, false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetIntegerInternal(int index, out int result, bool throwOnTypeMismatch)
+ {
+ EnsureIndex(index, true);
- object obj = this[index];
- //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21
- return obj switch
+ var value = this[index];
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- null => 0,
PdfInteger integer => integer.Value,
- PdfIntegerObject integerObject => integerObject.Value,
- _ => throw new InvalidCastException("GetInteger: Object is not an integer.")
+ PdfIntegerObject integer => integer.Value,
+ _ => Fail()
};
+ return success;
+
+ int Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfInteger), value.GetType()).Message);
+ }
+ success = false;
+ return 0;
+ }
}
///
- /// Converts the specified value to double.
- /// If the value does not exist, the function returns 0.
- /// If the value is not convertible, the function throws an InvalidCastException.
- /// If the index is out of range, the function throws an ArgumentOutOfRangeException.
+ /// Sets the entry to the specified value.
///
- public double GetReal(int index)
+ public void SetInteger(int index, int value)
+ {
+ EnsureIndex(index, true);
+
+ SetValueInternal(index, new PdfInteger(value));
+ }
+
+ // No GetUnsignedInteger or TryGetUnsignedInteger yet.
+ // Will be implemented if we find a use case.
+
+ // ===== PdfLongInteger =====
+
+ ///
+ /// Gets the long integer value for the specified index.
+ /// If the value exists but is neither an integer or a long integer
+ /// nor a PDF reference to an integer or a long integer,
+ /// the function throws an InvalidOperationException.
+ ///
+ public long GetLongInteger(int index)
+ {
+ TryGetLongIntegerInternal(index, out long result, true);
+ return result;
+ }
+
+ ///
+ /// Tries to get the long integer value for the specified index.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetLongInteger(int index, out long result)
+ => TryGetLongIntegerInternal(index, out result, false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetLongIntegerInternal(int index, out long result, bool throwOnTypeMismatch)
{
- if (index < 0 || index >= Count)
- throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3);
+ EnsureIndex(index, true);
- object obj = this[index];
- //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21
- if (obj is PdfReference reference)
+ var value = this[index];
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- //Debug.Assert(false, "Check why this is not PdfRealObject or PdfIntegerObject.");
+ PdfInteger integer => integer.Value,
+ PdfIntegerObject integer => integer.Value,
+ PdfLongInteger integer => integer.Value,
+ PdfLongIntegerObject integer => integer.Value,
+ _ => Fail()
+ };
+ return success;
- // ReSharper disable once RedundantCast
- obj = (object)reference.Value switch
+ long Fail()
+ {
+ if (throwOnTypeMismatch)
{
- PdfReal real => real,
- PdfInteger integer => integer,
- PdfRealObject realObject => realObject,
- PdfIntegerObject integerObject => integerObject,
- _ => throw new InvalidCastException("GetReal: Referenced object is not a number.")
- };
+ throw new InvalidOperationException(
+ SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfLongInteger), value.GetType()).Message);
+ }
+ success = false;
+ return 0;
}
+ }
+
+ ///
+ /// Sets the entry to the specified value.
+ ///
+ public void SetLongInteger(int index, long value)
+ {
+ SetValueInternal(index, new PdfLongInteger(value));
+ }
+
+ // ===== PdfReal =====
+
+ ///
+ /// Gets the real value for the specified index.
+ /// If the value exists but is neither an integer nor a real
+ /// nor a PDF reference to an integer or a real,
+ /// the function throws an InvalidOperationException.
+ ///
+ public double GetReal(int index)
+ {
+ TryGetRealInternal(index, out double result, true);
+ return result;
+ }
- return obj switch
+ ///
+ /// Tries to get the real value for the specified index.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetReal(int index, out double result)
+ => TryGetRealInternal(index, out result, false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetRealInternal(int index, out double result, bool throwOnTypeMismatch)
+ {
+ EnsureIndex(index, true);
+
+ var value = this[index];
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- null => 0,
- PdfReal real => real.Value,
- PdfRealObject realObject => realObject.Value,
PdfInteger integer => integer.Value,
- PdfIntegerObject integerObject => integerObject.Value,
- _ => throw new InvalidCastException("GetReal: Object is not a number.")
+ PdfIntegerObject integer => integer.Value,
+ PdfLongInteger integer => integer.Value,
+ PdfLongIntegerObject integer => integer.Value,
+ PdfReal real => real.Value,
+ PdfRealObject real => real.Value,
+ _ => Fail()
};
+ return success;
+
+ double Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfReal), value.GetType()).Message);
+ }
+ success = false;
+ return 0;
+ }
+ }
+
+ ///
+ /// Sets the entry to the specified value.
+ ///
+ public void SetReal(int index, double value)
+ {
+ SetValueInternal(index, new PdfReal(value));
}
+ // ===== PdfReal as nullable value =====
+
///
- /// Converts the specified value to double?.
- /// If the value does not exist, the function returns null.
- /// If the value is not convertible, the function throws an InvalidCastException.
- /// If the index is out of range, the function throws an ArgumentOutOfRangeException.
+ /// Gets the double? value for the specified index.
+ /// If the value exists but is neither a null object nor a real or integer
+ /// nor a PDF reference to a null object, a real or integer,
+ /// the function throws an InvalidOperationException.
///
- public double? GetNullableReal(int index)
+ public double? GetNullableReal(int index/*, double? defaultValue=*/)
{
- if (index < 0 || index >= Count)
- throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3);
+ // Note that this function exists for arrays only. It makes no sense for dictionaries,
+ // because entries like "/SomeNumber null" do not exist.
+
+ TryGetNullableRealInternal(index, out double? result, true);
+ return result;
+ }
+
+ ///
+ /// Tries to get the double? value for the specified index.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetNullableReal(int index, out double? result)
+ => TryGetNullableRealInternal(index, out result, false);
- object obj = this[index];
- //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21
- return obj switch
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetNullableRealInternal(int index, out double? result, bool throwOnTypeMismatch)
+ {
+ var value = this[index];
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- null => null,
PdfNull => null,
PdfNullObject => null,
- PdfReal real => real.Value,
- PdfRealObject realObject => realObject.Value,
PdfInteger integer => integer.Value,
- PdfIntegerObject integerObject => integerObject.Value,
- _ => throw new InvalidCastException("GetReal: Object is not a number.")
+ PdfIntegerObject integer => integer.Value,
+ PdfLongInteger integer => integer.Value,
+ PdfLongIntegerObject integer => integer.Value,
+ PdfReal real => real.Value,
+ PdfRealObject real => real.Value,
+ _ => Fail()
};
+ return success;
+
+ double? Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfReal), value.GetType()).Message);
+ }
+ success = false;
+ return null;
+ }
}
+ // No SetNullableReal because it makes no sense.
+
+ // ===== PdfString =====
+
///
- /// Converts the specified value to string.
- /// If the value does not exist, the function returns the empty string.
- /// If the value is not convertible, the function throws an InvalidCastException.
- /// If the index is out of range, the function throws an ArgumentOutOfRangeException.
+ /// Gets the string value for the specified index.
+ /// If the value exists but is neither a string nor a PDF reference to a string, the function throws an InvalidOperationException.
///
public string GetString(int index)
{
- if (index < 0 || index >= Count)
- throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3);
+ TryGetStringInternal(index, out string result, true);
+ return result;
+ }
+
+ ///
+ /// Tries to get the string value for the specified index.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetString(int index, out string result)
+ => TryGetStringInternal(index, out result, false);
- object obj = this[index];
- //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21
- return obj switch
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetStringInternal(int index, out string result, bool throwOnTypeMismatch)
+ {
+ var value = this[index];
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- null => "",
PdfString str => str.Value,
- PdfStringObject strObject => strObject.Value,
- _ => throw new InvalidCastException("GetString: Object is not a string.")
+ PdfStringObject str => str.Value,
+ _ => Fail()
};
+ return success;
+
+ string Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfString), value.GetType()).Message);
+ }
+ success = false;
+ return "";
+ }
}
///
- /// Converts the specified value to a name.
- /// If the value does not exist, the function returns the empty string.
- /// If the value is not convertible, the function throws an InvalidCastException.
- /// If the index is out of range, the function throws an ArgumentOutOfRangeException.
+ /// Sets the entry to the specified value.
+ ///
+ public void SetString(int index, string value)
+ {
+ SetValueInternal(index, new PdfString(value));
+ }
+
+ // ===== PdfName =====
+
+ ///
+ /// Gets the name value for the specified index.
+ /// If the value exists but is neither a name nor a PDF reference to a name,
+ /// the function throws an InvalidOperationException.
///
public string GetName(int index)
{
- if (index < 0 || index >= Count)
- throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3);
+ TryGetNameInternal(index, out string result, true);
+ return result;
+ }
- var obj = this[index];
- if (obj == null!)
- return "";
+ ///
+ /// Tries to get the name value for the specified index.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetName(int index, out string result)
+ => TryGetNameInternal(index, out result, false);
- var name = obj as PdfName;
- if (name != null!)
- return name.Value;
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetNameInternal(int index, out string result, bool throwOnTypeMismatch)
+ {
+ var value = this[index];
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
+ {
+ PdfName name => name.Value,
+ PdfNameObject name => name.Value,
+ _ => Fail()
+ };
+ return success;
+
+ string Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfInteger), value.GetType()).Message);
+ }
+ success = false;
+ return "/";
+ }
+ }
- var nameObject = obj as PdfNameObject;
- if (nameObject != null!)
- return nameObject.Value;
+ ///
+ /// Sets a PDF name at the specified index.
+ ///
+ public void SetName(int index, string value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
- throw new InvalidCastException("GetName: Object is not a name.");
+ // Ensure that value already starts with a slash.
+ if (value.Length == 0 || value[0] != '/')
+ {
+ PdfSharpLogHost.Logger.LogWarning("A PDF name must start with a '/'.");
+ value = String.Concat("/", value);
+ }
+ SetValueInternal(index, new PdfName(value));
+ }
+
+ // ===== GetValue =====
+
+ /// // TODO
+ /// Gets the value for the specified key.
+ /// Unsinn: If the value does not exist, it is optionally created.
+ ///
+ public PdfItem? GetValue(int index, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+#if PDFSHARP_DEBUG_
+ if (ShouldBreak5)
+ Debugger.Break();
+#endif
+ EnsureIndex(index, false);
+ EnsureNoCreationFlags(options);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ return GetValueInternal(index, options, valueType, valueType != null);
}
///
- /// Gets the PdfObject with the specified index, or null if no such object exists. If the index refers to
- /// a reference, the referenced PdfObject is returned.
+ /// * throws if valueType does not match
///
- public PdfObject? GetObject(int index)
+ public PdfItem GetRequiredValue(int index, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
{
- if (index < 0 || index >= Count)
- throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3);
+ return GetValue(index, options, valueType)
+ ?? throw ExceptionOnNull(index);
+ }
- var item = this[index];
- if (item is PdfReference reference)
- return reference.Value;
+ public bool TryGetValue(int index, [MaybeNullWhen(false)] out PdfItem value)
+ => TryGetValue(index, out value, null);
+
+ ///
+ /// * throws if result is not of type T.
+ ///
+ public bool TryGetValue(int index, [MaybeNullWhen(false)] out PdfItem value,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType)
+ {
+ value = null;
+ var result = GetValueInternal(index, VCF.NoTransform, valueType, false);
+ if (result != null)
+ {
+ value = result;
+ return true;
+ }
+ return false;
- return item as PdfObject;
+ //value = GetValueInternal(index, VCF.NoTransform, valueType, false);
+ //return value != null;
}
///
- /// Gets the PdfArray with the specified index, or null if no such object exists. If the index refers to
- /// a reference, the referenced PdfArray is returned.
+ /// * throws if result is not of type T.
///
- public PdfDictionary? GetDictionary(int index)
- => GetObject(index) as PdfDictionary;
+ public T? GetValue<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, VCF options = VCF.None) where T : PdfItem
+ {
+ EnsureIndex(index, false);
+ EnsureNoCreationFlags(options);
+
+ var value = (T?)GetValueInternal(index, options, typeof(T), true);
+ return value;
+ }
///
- /// Gets the PdfArray with the specified index, or null if no such object exists. If the index refers to
- /// a reference, the referenced PdfArray is returned.
+ /// * throws if result is not of type T.
///
- public PdfArray? GetArray(int index)
- => GetObject(index) as PdfArray;
+ public T GetRequiredValue<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, VCF options = VCF.None) where T : PdfItem
+ {
+ return GetValue(index, options)
+ ?? throw ExceptionOnNull(index);
+ }
+
+ public bool TryGetValue<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, [MaybeNullWhen(false)] out T value) where T : PdfItem
+ {
+ value = null;
+ if (GetValueInternal(index, VCF.NoTransform, typeof(T), false) is T valueOfT)
+ {
+ value = valueOfT;
+ return true;
+ }
+ return false;
+ }
///
- /// Gets the PdfReference with the specified index, or null if no such object exists.
+ /// Sets the entry with the specified value. DON’T USE THIS FUNCTION - IT MAY BE REMOVED. // PDFsharp/NT
+ ///
+ public void SetValue(int index, PdfItem value)
+ {
+ SetValueInternal(index, value);
+ }
+
+ // ===== GetObject =====
+
+ ///
+ /// Gets the PdfObject with the specified index, or null if no such object exists.
+ /// If the index refers to a reference, the referenced PdfObject is returned.
+ ///
+ public PdfObject? GetObject(int index, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ EnsureIndex(index, false);
+ EnsureNoCreationFlags(options);
+ if (valueType != null)
+ EnsureValueType(valueType);
+ //else
+ // valueType = typeof(PdfObject);
+
+ var value = GetValueInternal(index, options, valueType, false);
+ return value as PdfObject;
+ }
+
+ public PdfObject GetRequiredObject(int index, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ return GetObject(index, options, valueType)
+ ?? throw ExceptionOnNull(index);
+ }
+
+ ///
+ /// Gets a PDF object of type T from the specified index.
+ /// If the found object is a PDF reference or a PDF container (array or dictionary) but not of type T
+ /// it is tried to be transformed into this type and replaces the old object.
+ /// If the transformation is not possible, e.g. because T does not match with the found type,
+ /// an exception is thrown.
+ /// Returns null if the found object is neither a PDF reference nor a PDF container.
+ ///
+ /// The type of the PDF object to get.
+ /// The 0-based index of the object.
+ ///
+ public T? GetObject<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, VCF options = VCF.None) where T : PdfObject
+ {
+ EnsureIndex(index, false);
+ EnsureNoCreationFlags(options);
+
+ // TODO: Never throw?
+ bool throwOnTypeMismatch = typeof(PdfContainer).IsAssignableFrom(typeof(T));
+ return GetValueInternal(index, options, typeof(T), throwOnTypeMismatch) as T;
+ }
+
+ public T GetRequiredObject<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, VCF options = VCF.None) where T : PdfObject
+ {
+ return GetObject(index, options)
+ ?? throw ExceptionOnNull(index);
+ }
+
+ public void SetObject(int index, PdfObject obj) // Used in PDFsharp
+ {
+ EnsureIndex(index, false);
+
+ SetValueInternal(index, obj);
+ }
+
+ // ===== GetArray =====
+
+ ///
+ /// Gets the PdfArray with the specified index, or null if no such object exists.
+ /// If the index refers to a reference, the referenced PdfArray is returned.
+ ///
+ public PdfArray? GetArray(int index, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ EnsureIndex(index, false);
+ EnsureNoCreationFlags(options);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ return GetValueInternal(index, options, valueType, valueType != null) as PdfArray;
+ }
+
+ ///
+ /// Gets the PdfArray with the specified index.
+ /// An InvalidOperationException is thrown if the object does not exist.
+ /// If the index refers to a reference, the referenced PdfArray is returned.
+ ///
+ public PdfArray GetRequiredArray(int index, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ return GetArray(index, options, valueType)
+ ?? throw ExceptionOnNull(index);
+ }
+
+ public bool TryGetArray(int index, [MaybeNullWhen(false)] out PdfArray array)
+ => TryGetArray(index, out array, null);
+
+ public bool TryGetArray(int index, [MaybeNullWhen(false)] out PdfArray array,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType)
+ {
+ EnsureIndex(index, false);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ array = null;
+ if (GetValueInternal(index, VCF.NoTransform, valueType, false) is PdfArray value)
+ {
+ array = value;
+ return true;
+ }
+ return false;
+ }
+
+ public T? GetArray<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, VCF options = VCF.None) where T : PdfArray
+ {
+ EnsureIndex(index, false);
+ EnsureNoCreationFlags(options);
+
+ var array = GetValueInternal(index, options, typeof(T), true) as T;
+ return array;
+ }
+
+ public T GetRequiredArray<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, VCF options = VCF.None) where T : PdfArray
+ {
+ return GetArray(index, options)
+ ?? throw ExceptionOnNull(index);
+ }
+
+ public bool TryGetArray<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, [MaybeNullWhen(false)] out T array) where T : PdfArray
+ {
+ EnsureIndex(index, false);
+
+ array = null;
+ if (GetValueInternal(index, VCF.NoTransform, typeof(T), false) is T valueOfT)
+ {
+ array = valueOfT;
+ return true;
+ }
+ return false;
+ }
+
+ // ===== GetDictionary =====
+
+ ///
+ /// Gets the PdfDictionary with the specified index, or null if no such object exists.
+ /// If the index refers to a reference, the referenced PdfDictionary is returned.
+ ///
+ public PdfDictionary? GetDictionary(int index, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ EnsureIndex(index, false);
+ EnsureNoCreationFlags(options);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ return GetValueInternal(index, options, valueType, valueType != null) as PdfDictionary;
+ }
+
+ public PdfDictionary GetRequiredDictionary(int index, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ return GetDictionary(index, options, valueType)
+ ?? throw ExceptionOnNull(index);
+ }
+
+ public bool TryGetDictionary(int index, [MaybeNullWhen(false)] out PdfDictionary dict)
+ => TryGetDictionary(index, out dict, null);
+
+ public bool TryGetDictionary(int index, [MaybeNullWhen(false)] out PdfDictionary dict,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType)
+ {
+ EnsureIndex(index, false);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ dict = null;
+ if (GetValueInternal(index, VCF.NoTransform, valueType, false) is PdfDictionary value)
+ {
+ dict = value;
+ return true;
+ }
+ return false;
+ }
+
+ public T? GetDictionary<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, VCF options = VCF.None) where T : PdfDictionary
+ {
+ EnsureIndex(index, false);
+ EnsureNoCreationFlags(options);
+
+ var value = GetValueInternal(index, options, typeof(T), true);
+ return value as T;
+ }
+
+ public T GetRequiredDictionary<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, VCF options = VCF.None) where T : PdfDictionary
+ {
+ return GetDictionary(index, options)
+ ?? throw ExceptionOnNull(index);
+ }
+
+ public bool TryGetDictionary<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (int index, [MaybeNullWhen(false)] out T dict) where T : PdfDictionary
+ {
+ EnsureIndex(index, false);
+
+ dict = null;
+ if (GetValueInternal(index, VCF.NoTransform, typeof(T), false) is T valueOfT)
+ {
+ dict = valueOfT;
+ return true;
+ }
+ return false;
+ }
+
+ // ===== PdfReference =====
+
+ ///
+ /// Gets a PDF reference from the specified index, or null if no such object exists.
///
public PdfReference? GetReference(int index)
{
- var item = this[index];
- return item as PdfReference;
+ EnsureIndex(index, false);
+
+ return _elements[index] as PdfReference;
}
+ ///
+ /// Gets a PDF reference from the specified index, or throws an exception,
+ /// if no such object exists.
+ ///
+ public PdfReference GetRequiredReference(int index)
+ {
+ EnsureIndex(index, false);
+
+ return _elements[index] as PdfReference
+ ?? throw new InvalidOperationException(SyMsgs.IndirectReferenceMustNotBeNull.Message);
+ }
+
+ ///
+ /// Sets the entry to an indirect reference.
+ ///
+ public void SetReference(int index, PdfReference iref)
+ {
+ EnsureIndex(index, true);
+
+ if (iref is null)
+ throw new ArgumentNullException(nameof(iref));
+ SetValueInternal(index, iref);
+ }
+
+ // ===== PdfItem =====
+
///
/// Gets all items of this array.
///
public PdfItem[] Items => _elements.ToArray();
+ // ===== Internal =====
+
+ ///
+ /// Gets the value for the specified index.
+ /// There is no create option because a value cannot not exist.
+ /// A value can be optionally transformed to a derived type.
+ ///
+ PdfItem? GetValueInternal(int index, VCF options,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType, bool throwOnTypeMismatch)
+ {
+#if DEBUG_ // KEEP Test in more detail and report a bug. TODO
+ // TODO: Why leads 'var valueTest' to type 'PdfItem?'?
+ var valueTest2 = this[index]; // Is of type 'PdfItem' not of type 'PdfItem?'.
+ valueTest2 = null; // No Warning hint.
+#endif
+#if DEBUG_
+ // Are we reading a PDF document?
+ if ((OwningArray.Document?.IrefTable ?? null) is { IsUnderConstruction: true })
+ {
+ //Debugger.Break();
+ _ = typeof(int);
+ }
+#endif
+ Debug.Assert(options is VCF.Create or VCF.CreateIndirect is false);
+
+ // ReSharper suggested to use var, but this would make the type nullable. ???TODO: Why?
+ var valueTest = _elements[index]; // Is always of type PdfItem
+ // ReSharper disable once SuggestVarOrType_SimpleTypes because value is of type PdfItem? if I use var.
+ PdfItem value = _elements[index]; // Is always of type PdfItem.
+
+ Debug.Assert(value is not null);
+
+ // Case: The value exists and can be returned. But for imported documents check for necessary
+ // object type transformation.
+
+ if (value is PdfReference iref)
+ {
+#if DEBUG_
+ var irefTable = OwningArray.Document?.IrefTable ?? null;
+ if (irefTable is { IsUnderConstruction: true })
+ {
+ //Debug.Assert(false, "Should not happen anymore.");
+ _ = typeof(int);
+ }
+#endif
+ // Case: value is an indirect object. It can only be a container.
+
+#if true_ // TODO Create US for this case and DELETE the code.
+ // Check a very particular case first that can happen during
+ // the timespan when the IrefTable is not completely read.
+ var irefTable = OwningArray.Document?.IrefTable ?? null;
+ if (irefTable is { IsUnderConstruction: true })
+ {
+ // Case: During the import of a PDF document GetValue is called.
+ // This happens only during encryption.
+ // IMPROVE: Prevent from coming here. Check if we need temp irefs anymore.
+ var newIref = irefTable[iref.Value.ObjectID];
+ if (newIref == null)
+ {
+ // Case: Cannot happen. We can have irefs with no value, but not
+ // indirect objects with no reference.
+ _ = typeof(int);
+ }
+ else if (ReferenceEquals(iref, newIref) is false)
+ {
+ // Case: We are reading a PDF document that hast more than one XRef table or xref streams.
+ // A top level object can contain an indirect reference to an object with an ID that does not yet exist.
+ // TODO more checks...
+ if (ReferenceEquals(iref.Value, newIref.Value))
+ {
+ iref = newIref;
+ iref.Value.Reference = iref;
+ UpdateValueInternal(index, iref);
+ }
+ }
+ else
+ {
+ _ = typeof(int);
+ }
+ }
+#endif
+ // Is PdfReference explicitly requested?
+ if (valueType == typeof(PdfReference))
+ return value;
+
+ // In all other cases use the referenced value.
+ value = iref.Value;
+ if (value == null)
+ {
+ // If we come here PDF file is corrupted.
+ // TODO: What if we have a reference to a non-existing object?
+ throw new InvalidOperationException("Indirect reference without value.");
+ }
+
+ // The nesting can be simplified, but keep it as it is for better understandability.
+ if (options != VCF.NoTransform)
+ {
+ // //Get type from parameter or metadata.
+ // In contrast to PdfDictionary we have no metadata for arrays.
+ var type = valueType /*?? GetValueType(key)*/;
+
+ // Try transformation only once. If it fails, don’t try again.
+ // Should we try transformation?
+ if (value.ShouldTryTransformation
+ || type != null
+ || value.GetType().IsAssignableFrom(type))
+ {
+ // Do we have a type anyway?
+ if (type != null)
+ {
+ // Is value already of the expected type?
+ if (type.IsInstanceOfType(value))
+ {
+ // Case: The value is already of the appropriate type.
+
+ // Set to transformed, but only if the requested type is not a base type.
+ if (type != typeof(PdfDictionary) && type != typeof(PdfArray))
+ value.SetTransformed();
+ }
+ else if (value is PdfContainer cont)
+ {
+ // Case: Transform array or dictionary.
+
+ // TODO: Test to transform twice.
+
+ //Debug.Assert(cont.IsIndirect is false);
+ //Debug.Assert(cont.IsTransformed is false);
+ Debug.Assert(cont.ParentInfo is null);
+ value = CreateContainer(type, cont, cont.IsIndirect);
+ //Not for indirect objects UpdateValueInternal(index, value);
+ Debug.Assert(cont.IsDead);
+ Debug.Assert(value.IsTransformed);
+ Debug.Assert(cont.ParentInfo is null);
+ //Debug.Assert(((PdfContainer)value).ParentInfo is not null);
+ }
+ //else
+ //{
+ // throw new NotImplementedException("Type is not a PDF container.");
+ //}
+ else if (value is PdfPrimitiveObject)
+ {
+ throw new InvalidOperationException(
+ $"Primitive indirect object of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'.");
+ }
+ else
+ {
+ // Exotic case: Reference to e.g. PdfDocument.
+ // Just throw.
+ throw new InvalidOperationException(
+ $"Indirect object of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'.");
+ }
+
+ // TODO: Should fail because it is a Reference - test this.
+ // TODO: Do not call if value is same.
+ SetValueInternal(index, value);
+ }
+ else
+ {
+ // TODO: Should be possible to transform later.
+ value.SetTransformationTried();
+ }
+ }
+ }
+
+ Debug.Assert(value != null);
+ //return value;
+ }
+ else
+ {
+ // Case: value is a direct object.
+
+ // Transformation is possible after PDF import or the user
+ // creates a less derived container (e.g. in unit tests).
+ if (options != VCF.NoTransform)
+ {
+ // Try transformation only once. If it fails, don’t try again.
+ // Should we try transformation?
+ if (value.ShouldTryTransformation)
+ {
+ // In contrast to PdfDictionary we have no metadata for arrays.
+ var type = valueType; // There is no 'GetValueType(index)'.
+
+ // Do we have a type anyway?
+ if (type != null)
+ {
+ // Case: We have a type and an existing primitive or direct object.
+
+ // Handle special case PdfRectangle first.
+ // Make no sense in PdfArray.
+ if (type == typeof(PdfRectangle))
+ {
+ throw new InvalidOperationException(
+ "PdfRectangle is not an appropriate type for an item of a PdfArray.");
+ }
+ if (type.IsInstanceOfType(value))
+ {
+ // Case: The value is already of the appropriate type.
+
+ // Set to transformed, but only if the requested type is not a base type.
+ if (type != typeof(PdfDictionary) && type != typeof(PdfArray))
+ value.SetTransformed();
+ }
+ else if (value is PdfContainer cont)
+ {
+ // Case: Transform direct array or dictionary.
+
+ Debug.Assert(cont.IsIndirect is false);
+ Debug.Assert(cont.IsTransformed is false);
+ Debug.Assert(cont.ParentInfo is not null);
+
+ value = CreateContainer(type, cont, false /*cont.IsIndirect*/);
+ SetValueInternal(index, value);
+
+ Debug.Assert(cont.IsTransformed);
+ Debug.Assert(cont.IsDead);
+ Debug.Assert(value.IsTransformed); // TODO: Can transform twice?
+ Debug.Assert(cont.ParentInfo is null);
+ Debug.Assert(((PdfDictionary)value).ParentInfo is not null);
+ }
+ else
+ {
+ // Exotic case: value is not a PDF array or dictionary, but not from the requested type.
+ // Just throw.
+ throw new InvalidOperationException(
+ $"Value of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'.");
+ }
+ }
+ else
+ {
+ // TODO: Should be possible to transform later.
+ value.SetTransformationTried();
+ }
+ }
+ }
+ }
+
+ // Ensure not a type mismatch.
+ if (valueType != null)
+ {
+ if (!valueType.IsInstanceOfType(value))
+ {
+ if (throwOnTypeMismatch)
+ throw ExceptionOnTypeMismatch(index, valueType, value.GetType());
+ return null;
+ }
+ }
+ Debug.Assert(value != null); // TODO: Ensure.
+ return value;
+ }
+
+ ///
+ /// Implementation of SetValue.
+ /// Handles setting same value, setting indirect object,
+ /// and releasing old value.
+ /// Keep in sync with PdfDictionary.
+ ///
+ void SetValueInternal(int index, PdfItem value)
+ {
+#if DEBUG_
+ //if (ShouldBreak1)
+ // Debugger.Break();
+ if (value is PdfReference { ObjectID.ObjectNumber: 5 })
+ _ = typeof(int);
+
+#endif
+#if true_ // TODO REMOVE or throw exception in DEBUG
+#if DEBUG
+ // Are we reading a PDF document?
+ if ((OwningArray.Document?.IrefTable ?? null) is { IsUnderConstruction: true })
+ {
+ //Debugger.Break();
+ _ = typeof(int);
+ }
+#endif
+#endif
+ EnsureIndex(index, true);
+
+ // Already checked by caller.
+ Debug.Assert(value is not null);
+
+ if (value.IsDead)
+ throw new InvalidOperationException("TODO: Is Dead."); // /messages/ObjectIsDead.html
+
+ // Special treatment for PdfRectangle.
+ if (value is PdfRectangle rect)
+ value = rect.GetAsArrayOfValues();
+ else
+ PdfReference.ToReference(ref value);
+
+ PdfItem? oldItem = null;
+ if (index < Count)
+ {
+ oldItem = _elements[index];
+ if (ReferenceEquals(oldItem, value))
+ {
+ LogWarning();
+ return;
+ }
+ }
+ else
+ {
+ if (index > Count)
+ {
+ throw new IndexOutOfRangeException(
+ "Index must not be greater than Count."); // /messages/ObjectIsDead.html
+ }
+ // else: value is appended.
+ }
+
+ if (value is PdfObject obj)
+ {
+ if (obj.Reference != null)
+ {
+ Debug.Assert(false, "Should not come here anymore.");
+
+ // Case: Indirect object.
+
+ Debug.Assert(obj.ParentInfo is null, "An indirect object must not have a structure parent.");
+ value = obj.Reference;
+ if (ReferenceEquals(oldItem, value))
+ {
+ LogWarning();
+ return;
+ }
+ }
+ else if (obj is PdfPrimitiveObject)
+ {
+ // Case: Direct primitive object.
+ // E.g. non-indirect PdfStringObject is used instead of PdfString.
+ FailForDirectPrimitiveObject(obj);
+ }
+ else
+ {
+ // Case: Direct container object.
+ Debug.Assert(obj is PdfContainer);
+
+ if (obj.ParentInfo != null)
+ throw new InvalidOperationException("A direct object can only be added once.");
+
+ obj.SetStructureParent(this, index);
+ }
+ }
+ else
+ {
+ // Case: value is just a PdfItem - nothing special to do.
+ // Case: value is PDF reference or primitive - nothing special to do.
+ Debug.Assert(value is PdfReference or PdfPrimitive);
+ }
+
+ if (index < Count)
+ _elements[index] = value;
+ else
+ _elements.Add(value);
+
+ // oldItem can be null here if index was equal to Count
+ // or value was inserted.
+ if (oldItem != null)
+ ReleaseItem(oldItem);
+
+ return;
+
+ void LogWarning()
+ {
+ //PdfSharpLogHost.Logger.LogWarning("Setting same value in dictionary.");
+ }
+ }
+
+ ///
+ /// The array this elements object belongs to.
+ ///
+ PdfArray OwningArray => (PdfArray)OwningContainer;
+
#region IList Members
///
@@ -371,15 +1501,21 @@ public string GetName(int index)
///
/// Gets or sets an item at the specified index.
///
- ///
public PdfItem this[int index]
{
+ // Always get the raw value.
get => _elements[index];
set
{
- if (value == null!)
+ if (value == null)
throw new ArgumentNullException(nameof(value));
- _elements[index] = value;
+
+ //// Note that if index is Count value is appended.
+ //if (index < 0 || index > Count)
+ // throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3);
+ EnsureIndex(index, true);
+
+ SetValueInternal(index, value);
}
}
@@ -388,7 +1524,10 @@ public PdfItem this[int index]
///
public void RemoveAt(int index)
{
+ var oldItem = _elements[index];
_elements.RemoveAt(index);
+ ReleaseItem(oldItem);
+ FixIndexInParentInfo(index, -1);
}
///
@@ -396,7 +1535,13 @@ public void RemoveAt(int index)
///
public bool Remove(PdfItem item)
{
- return _elements.Remove(item);
+ int index = IndexOf(item);
+ if (index >= 0)
+ {
+ RemoveAt(index);
+ return true;
+ }
+ return false;
}
///
@@ -404,7 +1549,14 @@ public bool Remove(PdfItem item)
///
public void Insert(int index, PdfItem value)
{
- _elements.Insert(index, value);
+ // TODO: Check reuse, check moved objects.
+ //_elements.Insert(index, value);
+ // TODO: Let the element know its index.
+
+ // Make space by inserting a dummy value.
+ _elements.Insert(index, PdfNull.Value);
+ FixIndexInParentInfo(index + 1, 1);
+ SetValueInternal(index, value);
}
///
@@ -412,6 +1564,9 @@ public void Insert(int index, PdfItem value)
///
public bool Contains(PdfItem value)
{
+ if (value is PdfObject { Reference: not null } obj)
+ value = obj.Reference;
+
return _elements.Contains(value);
}
@@ -420,7 +1575,10 @@ public bool Contains(PdfItem value)
///
public void Clear()
{
+ var oldItems = _elements.ToArray();
_elements.Clear();
+ foreach (var item in oldItems)
+ ReleaseItem(item);
}
///
@@ -428,6 +1586,12 @@ public void Clear()
///
public int IndexOf(PdfItem value)
{
+ if (value is PdfReference iref)
+ return _elements.IndexOf(value);
+
+ if (value is PdfObject { Reference: not null } obj)
+ return _elements.IndexOf(obj.RequiredReference);
+
return _elements.IndexOf(value);
}
@@ -436,14 +1600,10 @@ public int IndexOf(PdfItem value)
///
public void Add(PdfItem value)
{
- // TODO_OLD: ???
- //Debug.Assert((value is PdfObject && ((PdfObject)value).Reference == null) | !(value is PdfObject),
- // "You try to set an indirect object directly into an array.");
+ if (value == null!)
+ throw new ArgumentNullException(nameof(value));
- if (value is PdfObject { IsIndirect: true } obj)
- _elements.Add(obj.Reference!);
- else
- _elements.Add(value);
+ SetValueInternal(Count, value);
}
///
@@ -469,17 +1629,13 @@ public void Add(PdfItem value)
/// Copies the elements of the array to the specified array.
///
public void CopyTo(PdfItem[] array, int index)
- {
- _elements.CopyTo(array, index);
- }
+ => _elements.CopyTo(array, index);
///
/// The current implementation return null.
///
public object SyncRoot => null!;
- #endregion
-
///
/// Returns an enumerator that iterates through the array.
///
@@ -489,34 +1645,77 @@ public IEnumerator GetEnumerator()
IEnumerator IEnumerable.GetEnumerator()
=> _elements.GetEnumerator();
+ #endregion
+
///
- /// The elements of the array.
+ /// If an item is added or removed from the array, the subsequent direct object’s
+ /// index in their ParentInfo must be adjusted accordingly.
///
- List _elements;
+ /// Index of the first item to be adjusted.
+ /// 1 or -1, depending on insert or delete.
+ void FixIndexInParentInfo(int index, int offset)
+ {
+ for (int idx = index; idx < _elements.Count; idx++)
+ {
+ var obj = _elements[idx] as PdfObject;
+ var parentInfo = obj?.ParentInfo;
+ if (parentInfo is { IsArray: true })
+ parentInfo.AdjustIndex(offset);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void EnsureIndex(int index, bool writeValue)
+ {
+ // TODO: IL code shows that the function is not inlined.
+ // Even in release build. Analyse why.
+
+ var count = _elements.Count;
+ if (index < 0 || writeValue ? index > count : index >= count)
+ ThrowIndexOutOfRange(index, writeValue);
+ }
+
+ void ThrowIndexOutOfRange(int index, bool writeValue)
+ {
+ throw new IndexOutOfRangeException(
+ $"Index '{index}' is out of range for {(writeValue ? "writing into" : "reading from")} PdfArray.");
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void EnsureNoCreationFlags(VCF options)
+ {
+ if (options is VCF.Create or VCF.CreateIndirect)
+ ThrowCreationFlags(options);
+ }
+
+ void ThrowCreationFlags(VCF options)
+ {
+ throw new InvalidOperationException(
+ $"Flag 'ACF.{options.ToString()}' must not be set for array elements because an array entry cannot be undefined.");
+ }
+
+ Exception ExceptionOnNull(int index)
+ {
+ return new InvalidOperationException($"Value at index '{index}' has wrong type.");
+ }
+
+ Exception ExceptionOnTypeMismatch(int index, Type expected, Type found)
+ {
+ return new InvalidOperationException($"Value at index '{index}' is expected to be of type {expected.FullName}, but is of type {found.FullName}.");
+ }
///
- /// The array this object belongs to.
+ /// The elements of the array.
///
- PdfArray? _ownerArray;
+ List _elements = [];
}
- ArrayElements? _elements;
-
///
/// Gets the DebuggerDisplayAttribute text.
///
// ReSharper disable UnusedMember.Local
string DebuggerDisplay
+ => Invariant($"{GetType().Name}({ObjectID.DebuggerDisplay}, count={_elements?.Count ?? 0})");
// ReSharper restore UnusedMember.Local
- {
- get
- {
-#if true
- return String.Format(CultureInfo.InvariantCulture, "array({0},[{1}])", ObjectID.DebuggerDisplay, _elements?.Count ?? 0);
-#else
- return String.Format(CultureInfo.InvariantCulture, "array({0},[{1}])", ObjectID.DebuggerDisplay, _elements == null ? 0 : _elements.Count);
-#endif
- }
- }
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayExtensions/PdfArrayExtensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayExtensions/PdfArrayExtensions.cs
new file mode 100644
index 00000000..48a346da
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayExtensions/PdfArrayExtensions.cs
@@ -0,0 +1,35 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+namespace PdfSharp.Pdf.PdfArrayExtensions // #PDFsharp/NT
+{
+ ///
+ /// Extension methods for PDF arrays. NYI
+ ///
+ public static class PdfArrayExtensions
+ {
+ /////
+ ///// NYI
+ /////
+ /////
+ /////
+ /////
+ //public static PdfItem GetRawValue(this PdfArray array, int index)
+ //{
+ // return array.Elements[index];
+ //}
+
+ //public static PdfItem GetItem(this PdfArray array, int index)
+ //{
+ // return array.Elements[index];
+ //}
+
+ //public static void SetItem(this PdfArray array, int index, PdfItem value)
+ //{
+ // if (value == null)
+ // throw new ArgumentNullException(nameof(value));
+
+ // array.Elements.SetValue(index, value);
+ //}
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayOfDictionaries.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayOfDictionaries.cs
new file mode 100644
index 00000000..63db1545
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayOfDictionaries.cs
@@ -0,0 +1,66 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PdfSharp.Pdf
+{
+ ///
+ /// Represents a PdfArray of PDF dictionaries.
+ /// Used e.g. for the /AF entry in the PDF catalog.
+ ///
+ public class PdfArrayOfDictionaries : PdfArray
+ {
+ ///
+ /// Initialize a new instance of this class.
+ ///
+ public PdfArrayOfDictionaries()
+ { }
+
+ ///
+ /// Initializes a new instance of this class.
+ ///
+ public PdfArrayOfDictionaries(PdfDocument document)
+ : base(document)
+ { }
+
+ ///
+ /// Initializes a new instance from an existing object. Used for object type transformation.
+ ///
+ public PdfArrayOfDictionaries(PdfArray array)
+ : base(array)
+ { }
+
+ ///
+ /// Adds a PDF dictionary to the array.
+ /// The dictionary must be an indirect object.
+ ///
+ /// The indirect dictionary to add.
+ public void AddDictionary(PdfDictionary dict)
+ {
+ var reference = dict.Reference
+ ?? throw new ArgumentException("Dictionary must be an indirect object.", nameof(dict));
+ if (Elements.Contains(reference))
+ throw new InvalidOperationException("Dictionary already in array.");
+ Elements.Add(reference);
+ }
+
+ ///
+ /// Removes a PDF dictionary from the array.
+ /// Returns true if the dictionary was successfully removed, false otherwise.
+ ///
+ ///
+ public bool RemoveDictionary(PdfDictionary dict)
+ {
+ var reference = dict.Reference
+ ?? throw new ArgumentException("Dictionary must be an indirect object.", nameof(dict));
+
+ var result = Elements.Remove(reference);
+ return result;
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs
index 380b66c4..4e300532 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using PdfSharp.Pdf.IO;
@@ -9,7 +9,7 @@ namespace PdfSharp.Pdf
/// Represents a direct boolean value.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
- public sealed class PdfBoolean : PdfItem
+ public sealed class PdfBoolean : PdfPrimitive
{
///
/// Initializes a new instance of the class.
@@ -47,7 +47,7 @@ public override string ToString()
=> Value ? bool.TrueString : bool.FalseString;
///
- /// Writes 'true' or 'false'.
+ /// Writes ‘true’ or ‘false’.
///
internal override void WriteObject(PdfWriter writer)
=> writer.Write(this);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBooleanObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBooleanObject.cs
index cf463f0c..1abe007b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBooleanObject.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBooleanObject.cs
@@ -10,7 +10,7 @@ namespace PdfSharp.Pdf
/// an external PDF file, the value is converted into a direct object.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
- public sealed class PdfBooleanObject : PdfObject
+ public sealed class PdfBooleanObject : PdfPrimitiveObject
{
///
/// Initializes a new instance of the class.
@@ -30,7 +30,21 @@ public PdfBooleanObject(bool value)
/// Initializes a new instance of the class.
///
public PdfBooleanObject(PdfDocument document, bool value)
- : base(document)
+ : base(document, true)
+ {
+ _value = value;
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// without making it indirect.
+ /// Used in PDF parser only.
+ ///
+ /// The document.
+ /// The initial value.
+ /// If true creates an indirect object.
+ internal PdfBooleanObject(PdfDocument document, bool value, bool createIndirect)
+ : base(document, createIndirect)
{
_value = value;
}
@@ -45,7 +59,7 @@ public PdfBooleanObject(PdfDocument document, bool value)
///
/// Returns "false" or "true".
///
- public override string ToString()
+ public override string ToString()
=> _value ? bool.TrueString : bool.FalseString;
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfContainer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfContainer.cs
new file mode 100644
index 00000000..da11cf04
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfContainer.cs
@@ -0,0 +1,75 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+// v7.0.0 Ready
+
+namespace PdfSharp.Pdf
+{
+ // Re/Sharper enable GrammarMistakeInComment
+
+ ///
+ /// Common abstract base class for both PdfArray and PdfDictionary.
+ /// For technical purposes only, e.g. as marker class. There is no counterpart of
+ /// this class in the PDF specification.
+ ///
+ public abstract class PdfContainer : PdfObject
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected internal PdfContainer()
+ {
+ // Only PdfArray or PdfDictionary are allowed to derive from PdfContainer.
+ Debug.Assert(this is PdfArray or PdfDictionary);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected internal PdfContainer(PdfDocument doc, bool createIndirect = false)
+ : base(doc, createIndirect)
+ {
+ // Only PdfArray or PdfDictionary are allowed to derive from PdfContainer.
+ Debug.Assert(this is PdfArray or PdfDictionary);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected internal PdfContainer(PdfContainer obj) : base(obj)
+ {
+ // Only PdfArray or PdfDictionary are allowed to derive from PdfContainer.
+ Debug.Assert(this is PdfArray or PdfDictionary);
+ }
+
+ ///
+ /// Transforms a container to a derived. If the container already is of the requested type,
+ /// no action is taken.
+ ///
+ protected internal T TransformTo<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (PdfContainer obj) where T : PdfContainer
+ {
+ //if (typeof(T).IsInstanceOfType(obj))//obj.GetType().IsAssignableTo(typeof(T))))
+ if (obj is T result) //obj.GetType().IsAssignableTo(typeof(T))))
+ return result;
+
+ Debug.Assert(obj.GetType().IsAssignableFrom(typeof(T)));
+
+ switch (obj)
+ {
+ case PdfDictionary dict:
+ result = (T)dict.Elements.CreateContainer(typeof(T), obj, dict.IsIndirect);
+ return result;
+
+ case PdfArray array:
+ result = (T)array.Elements.CreateContainer(typeof(T), obj, array.IsIndirect);
+ return result;
+
+ default:
+ throw new InvalidOperationException("Object is neither a PDF array nor a dictionary.");
+ }
+ }
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValue.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValue.cs
index 913ffd31..9cce5162 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValue.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValue.cs
@@ -30,6 +30,10 @@ internal PdfCustomValue(PdfDocument document)
CreateStream([]);
}
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfCustomValue(PdfDictionary dict)
: base(dict)
{
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValues.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValues.cs
index db3b7faa..1b377218 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValues.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValues.cs
@@ -17,6 +17,10 @@ internal PdfCustomValues(PdfDocument document)
: base(document)
{ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfCustomValues(PdfDictionary dict)
: base(dict)
{ }
@@ -60,7 +64,7 @@ public PdfCustomValue? this[string key]
else
{
Owner.Internals.AddObject(value);
- Elements.SetReference(key, value);
+ Elements.SetObject(key, value);
}
}
#if old
@@ -108,13 +112,13 @@ public static void ClearAllCustomValues(PdfDocument document)
internal static PdfCustomValues Get(DictionaryElements elem)
{
- string key = elem.Owner.Owner.Internals.CustomValueKey;
+ string key = elem.OwningContainer.Owner.Internals.CustomValueKey;
PdfCustomValues? customValues;
var dict = elem.GetDictionary(key);
if (dict == null)
{
customValues = new PdfCustomValues();
- elem.Owner.Owner.Internals.AddObject(customValues);
+ elem.OwningContainer.Owner.Internals.AddObject(customValues);
elem.Add(key, customValues);
}
else
@@ -128,7 +132,7 @@ internal static PdfCustomValues Get(DictionaryElements elem)
internal static void Remove(DictionaryElements elem)
{
- elem.Remove(elem.Owner.Owner.Internals.CustomValueKey);
+ elem.Remove(elem.OwningContainer.Owner.Internals.CustomValueKey);
}
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs
index b5bb90de..61c60354 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs
@@ -1,50 +1,136 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
using PdfSharp.Pdf.IO;
+// v7.0.0 Ready
+
namespace PdfSharp.Pdf
{
///
/// Represents a direct date value.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
- public sealed class PdfDate : PdfItem
+ public sealed class PdfDate : PdfPrimitive
{
+ // Reference 2.0: 7.9.4 Dates / Page 717
+ // See also: https://learn.microsoft.com/en-us/dotnet/standard/datetime/converting-between-datetime-and-offset
+
///
/// Initializes a new instance of the class.
+ /// The Value property becomes null.
///
public PdfDate()
{ }
///
/// Initializes a new instance of the class.
+ /// If the string is not a valid PDF date the Value property becomes null.
///
- public PdfDate(string value)
+ public PdfDate(string date)
{
- Value = Parser.ParseDateTime(value, DateTime.MinValue);
+ if (!Parser.TryParseDate(date, out var value))
+ {
+ var message = $"""The string "{date}" is not a valid PDF date.""";
+ PdfSharpLogHost.PdfReadingLogger.LogError(message);
+ }
+ Value = value;
}
///
/// Initializes a new instance of the class.
///
+ [Obsolete("Use a DateTimeOffset for creating a PdfDate.")]
public PdfDate(DateTime value)
+ {
+ // Let .NET do the conversion. We do not try to ‘optimize’ it.
+ Value = value;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfDate(DateTimeOffset value)
{
Value = value;
}
///
- /// Gets the value as DateTime.
+ /// Gets the value as DateTimeOffset.
+ /// Use ToString to get the PDF date string.
+ /// The value is null if the class was not initialized with a valid PDF date.
///
- public DateTime Value { get; }
+ public DateTimeOffset? Value { get; }
///
- /// Returns the value in the PDF date format.
+ /// Returns the value as a PDF date formatted string,
+ /// or the empty string if no value was set.
+ /// The string looks like (D:YYYYMMDDHHmmSSOHH'mm').
///
public override string ToString()
{
- string delta = Value.ToString("zzz").Replace(':', '\'');
- return $"D:{Value:yyyyMMddHHmmss}{delta}'";
+ return ToPdfString(Value);
+
+ // #DELETE 25-12-31
+ //////#if old_code
+ //// if (Value == null)
+ //// return "";
+
+ //// // TO/DO clean this all up #US270 / use DateTimeOffset instead of Date/Time
+ ////#if DEBUG_
+ //// // TO/DO https://stackoverflow.com/questions/65004352/c-sharp-datetime-tostring-with-zzz-breaks-in-dotnet-framework-but-not-in-dotnet
+ //// var kind = Value.Kind;
+ //// // TO/DO Here we have a difference between .NET Framework and .NET
+ //// // See also SpecifyLocalDateTimeKindIfUnspecified
+ //// // Date/Time today = Date/Time.UtcNow;
+ //// // Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", today));
+ //// // // Displays -7, -07, -07:00 on .NET Framework
+ //// // // Displays +0, +00, +00:00 on .NET Core and .NET 5+
+ ////#endif
+
+ //// // Fix bug in .NET Framework:
+ //// // offset is always "00'00" in UTC (that’s why it’s called UTC).
+ //// string offset = Value.Kind == DateTimeKind.Utc
+ //// ? "00'00"
+ //// : Value.ToString("zzz").Replace(':', '\'');
+ //// // The trailing ‘'’ was part of the Syntax in PDF 1.x.
+ //// // Since PDF 2.0 it is not part of the syntax anymore.
+ //// // return $"D:{Value:yyyyMMddHHmmss}{offset}'";
+
+ //// // Page 118ff
+ //// // NOTE 1 A date string can be any valid PDF string object as described in 7.3.4, "String objects".
+ //// // The description above relates to the text string value after appropriate processing.
+ //// // NOTE 2 PDF versions up to and including 1.7 defined a date string to include a terminating apostrophe.
+ //// // PDF processors are recommended to accept date strings that still follow that convention.
+ //// // NOTE 3 The letter Z can optionally be followed by hour and minute offsets, which are zero in this case.
+
+ //// // We keep trailing ‘'’ and write ‘Z00'00’ instead of ‘Z’ for maximum compatibility.
+ //// var result = Value.Kind == DateTimeKind.Utc
+ //// ? $"D:{Value:yyyyMMddHHmmss}Z00'00'" // TO/DO verify: "Z" or "Z00'00"?
+ //// : $"D:{Value:yyyyMMddHHmmss}{Value.ToString("zzz").Replace(':', '\'')}'";
+ //// return result;
+ ////#endif
+ }
+
+ ///
+ /// Converts a DateTimeOffset into a PDF date string.
+ /// Returns the empty string if value is null.
+ ///
+ static string ToPdfString(DateTimeOffset? dateTimeOffset)
+ {
+ var result = "";
+ if (dateTimeOffset != null)
+ {
+ result = $"D:{dateTimeOffset:yyyyMMddHHmmss}";
+ if (dateTimeOffset.Value.Offset == TimeSpan.Zero)
+ result += "Z";
+ else
+ // We keep trailing ‘'’ if offset is not 0 for maximum compatibility.
+ result += dateTimeOffset.Value.ToString("zzz").Replace(':', '\'') + '\'';
+ }
+ return result;
}
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs
index 062cd9c8..3dc6bed9 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs
@@ -1,109 +1,115 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using System.Runtime.CompilerServices;
using System.Collections;
-using System.Reflection;
using System.Text;
+using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
+using PdfSharp.Logging;
using PdfSharp.Drawing;
-using PdfSharp.Pdf.IO;
-using PdfSharp.Pdf.Filters;
using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Filters;
using PdfSharp.Pdf.Internal;
-using System.Diagnostics.CodeAnalysis;
+using PdfSharp.Pdf.IO;
+
+// TODO REMOVE
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf
{
- ///
- /// Value creation flags. Specifies whether and how a value that does not exist is created.
- ///
- // ReSharper disable InconsistentNaming
- public enum VCF
- // ReSharper restore InconsistentNaming
- {
- ///
- /// Don’t create the value.
- ///
- None,
-
- ///
- /// Create the value as direct object.
- ///
- Create,
-
- ///
- /// Create the value as indirect object.
- ///
- CreateIndirect,
- }
-
///
/// Represents a PDF dictionary object.
///
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
- public class PdfDictionary : PdfObject, IEnumerable>
+ public class PdfDictionary : PdfContainer, IEnumerable> // TODO: explain why not PdfItem? anymore
{
- // Reference: 3.2.6 Dictionary Objects / Page 59
+ // Reference 1.7: 3.2.6 Dictionary Objects / Page 59
+ // Reference 2.0: 7.3.7 Dictionary objects / Page 30
///
/// Initializes a new instance of the class.
///
public PdfDictionary()
- { }
+ {
+ // Direct object.
+ ItemFlags |= ItemFlags.IsDictionary;
+ }
///
/// Initializes a new instance of the class.
///
/// The document.
- public PdfDictionary(PdfDocument document)
- : base(document)
- { }
+ /// If true creates an indirect object.
+ public PdfDictionary(PdfDocument document, bool createIndirect = false)
+ : base(document, createIndirect)
+ {
+ ItemFlags |= ItemFlags.IsDictionary;
+ }
///
- /// Initializes a new instance from an existing dictionary. Used for object type transformation.
+ /// Initializes a new instance from an existing dictionary.
+ /// Used for object type transformation.
///
protected PdfDictionary(PdfDictionary dict)
: base(dict)
{
- if (dict._elements != null)
- dict._elements.ChangeOwner(this);
- if (dict.Stream != null!)
- dict.Stream.ChangeOwner(this);
+#if DEBUG
+ // Protect against unintended invocation.
+ var oldType = dict.GetType();
+ if (oldType != typeof(PdfDictionary))
+ {
+ var newType = GetType();
+ if (oldType == newType)
+ {
+ throw new InvalidOperationException($"You try to convert a PDF dictionary into type '{newType.FullName}', " +
+ $"but the dictionary is already of this type.");
+ }
+
+ if (!oldType.IsAssignableFrom(newType))
+ {
+ throw new InvalidOperationException($"You try to convert type '{oldType.FullName}' into type '{newType.FullName}', " +
+ $"but '{newType.Name}' is not derived from '{oldType.Name}'.");
+ }
+ }
+#endif
+ // Move ownership of elements and stream to this instance.
+ dict._elements?.ChangeOwner(this);
+ dict.Stream?.ChangeOwner(this);
+ dict.SetDead();
+ ItemFlags |= ItemFlags.IsDictionary | ItemFlags.IsTransformed;
}
///
- /// Creates a copy of this dictionary. Direct values are deep copied. Indirect references are not
- /// modified.
+ /// Creates a copy of this dictionary.
+ /// Direct values are deep-copied. Indirect references are not modified.
///
public new PdfDictionary Clone()
=> (PdfDictionary)Copy();
- ///
+ /// // TODO check
/// This function is useful for importing objects from external documents. The returned object is not
/// yet complete. irefs refer to external objects and directed objects are cloned but their document
/// property is null. A cloned dictionary or array needs a 'fix-up' to be a valid object.
///
protected override object Copy()
{
+ // Clone dictionary.
var dict = (PdfDictionary)base.Copy();
- if (dict._elements != null)
+ var elements = dict._elements;
+ if (elements != null)
{
- dict._elements = dict._elements.Clone();
- dict._elements.ChangeOwner(dict);
- var names = dict._elements.KeyNames;
- foreach (var name in names)
- {
- if (dict._elements[name] is PdfObject obj)
- {
- obj = obj.Clone();
- // Recall that obj.Document is now null.
- dict._elements[name] = obj;
- }
- }
+ elements = elements.Clone();
+ dict._elements = elements;
+ elements.ChangeOwner(dict);
}
- if (dict.Stream != null!)
+
+ var stream = dict.Stream;
+ if (stream != null)
{
- dict.Stream = dict.Stream.Clone();
- dict.Stream.ChangeOwner(dict);
+ stream = stream.Clone();
+ dict.Stream = stream;
+ stream.ChangeOwner(dict);
}
return dict;
}
@@ -111,45 +117,62 @@ protected override object Copy()
///
/// Gets the dictionary containing the elements of this dictionary.
///
- public DictionaryElements Elements => _elements ??= new DictionaryElements(this);
+ public DictionaryElements Elements
+ {
+ get
+ {
+ EnsureAlive();
+ return _elements ??= new(this);
+ }
+ set
+ {
+ if (_elements != null && value != null)
+ throw new InvalidOperationException("Elements cannot be set if already created.");
+ _elements = value;
+ }
+ }
///
/// The elements of the dictionary.
///
- // ReSharper disable once InconsistentNaming
- internal DictionaryElements? _elements;
+ DictionaryElements? _elements;
///
/// Returns an enumerator that iterates through the dictionary elements.
///
- public IEnumerator> GetEnumerator()
+ public IEnumerator> GetEnumerator()
=> Elements.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
///
- /// Returns a string with the content of this object in a readable form. Useful for debugging purposes only.
+ /// Returns a string with the content of this object in a readable form.
+ /// Useful for debugging purposes only.
///
public override string ToString()
{
// Get keys and sort.
PdfName[] keys = Elements.KeyNames;
List list = [.. keys];
- list.Sort(PdfName.Comparer);
+ list.Sort(/*PdfName.*/Comparer);
list.CopyTo(keys, 0);
- var pdf = new StringBuilder();
- pdf.Append("<< ");
- foreach (PdfName key in keys)
- pdf.Append(key + " " + Elements[key] + " ");
- pdf.Append(">>");
+ var text = new StringBuilder();
+ text.Append("<<");
+ foreach (var key in keys)
+ text.Append(key + " " + Elements[key] + " "); // Need PdfReference here.
+ text.Append(">>");
- return pdf.ToString();
+ return text.ToString();
}
internal override void WriteObject(PdfWriter writer)
{
+#if DEBUG
+ if (ObjectID.ObjectNumber == 1)
+ _ = typeof(int);
+#endif
writer.WriteBeginObject(this);
//int count = Elements.Count;
PdfName[] keys = Elements.KeyNames;
@@ -159,7 +182,6 @@ internal override void WriteObject(PdfWriter writer)
if (Stream != null)
Debug.Assert(Elements.ContainsKey("/Length"), "Dictionary has a stream but no length is set.");
#endif
-
if (_stream is not null && writer.EffectiveSecurityHandler != null)
{
// Encryption could change the size of the stream.
@@ -170,16 +192,14 @@ internal override void WriteObject(PdfWriter writer)
Elements[PdfStream.Keys.Length] = new PdfInteger(_stream?.Length ?? 0);
}
-#if DEBUG
// Sort keys for debugging purposes. Comparing PDF files with for example programs like
// Araxis Merge is easier with sorted keys.
- if (writer.Layout == PdfWriterLayout.Verbose)
+ if (writer.IsVerboseLayout)
{
var list = new List(keys);
- list.Sort(PdfName.Comparer);
+ list.Sort(/*PdfName.*/Comparer);
list.CopyTo(keys, 0);
}
-#endif
foreach (var key in keys)
WriteDictionaryElement(writer, key);
@@ -189,45 +209,50 @@ internal override void WriteObject(PdfWriter writer)
}
///
- /// Writes a key/value pair of this dictionary. This function is intended to be overridden
- /// in derived classes.
+ /// Writes a key-value pair of this dictionary.
+ /// This function is intended to be overridden in derived classes.
///
- internal virtual void WriteDictionaryElement(PdfWriter writer, PdfName key)
+ internal void WriteDictionaryElement(PdfWriter writer, PdfName key)
{
- if (key == null)
- throw new ArgumentNullException(nameof(key));
- var item = Elements[key];
-#if DEBUG
- // TODO_OLD: simplify PDFsharp
- if (item is PdfObject { IsIndirect: true } pdfObject)
- {
- // Replace an indirect object by its Reference.
- item = pdfObject.Reference;
- Debug.Assert(false, "Check when we come here.");
- }
-#endif
+ Debug.Assert(key != null);
+ var item = Elements[key]!; // We need references here. GetValue is harmful and leads to broken files. // #US373
key.WriteObject(writer);
- item?.WriteObject(writer);
- writer.NewLine();
+ //if (writer.Layout == PdfWriterLayout.Verbose)
+ // writer.WriteSpace();
+ item.WriteObject(writer);
+ if (writer.IsVerboseLayout)
+ writer.NewLine();
}
///
- /// Writes the stream of this dictionary. This function is intended to be overridden
- /// in a derived class.
+ /// Writes the stream of this dictionary.
+ /// This function is intended to be overridden in a derived class.
///
internal virtual void WriteDictionaryStream(PdfWriter writer)
{
+ if (!IsIndirect)
+ throw new InvalidOperationException("Direct PDF dictionary must not have a PDF stream.");
+
writer.WriteStream(this, (writer.Options & PdfWriterOptions.OmitStream) == PdfWriterOptions.OmitStream);
}
///
- /// Gets or sets the PDF stream belonging to this dictionary. Returns null if the dictionary has
+ /// Gets the PDF stream belonging to this dictionary. Returns null if the dictionary has
/// no stream. To create the stream, call the CreateStream function.
///
- public PdfStream Stream
+ public PdfStream? Stream
{
- get => _stream!; // ?? NRT.ThrowOnNull(); // Can be null.
- set => _stream = value;
+ get
+ {
+ EnsureAlive();
+ return _stream;
+ }
+
+ internal set
+ {
+ EnsureAlive();
+ _stream = value;
+ }
}
PdfStream? _stream;
@@ -237,37 +262,97 @@ public PdfStream Stream
///
public PdfStream CreateStream(byte[] value)
{
+ EnsureAlive();
+
+ // Ensure that the object is an indirect object.
+
if (_stream != null)
throw new InvalidOperationException("The dictionary already has a stream.");
- Stream = new PdfStream(value, this);
+ // OK, you can create a PDF stream for a direct object. But it must become indirect before saving.
+ //if (!IsIndirect)
+ // throw new InvalidOperationException("Cannot create a stream for a direct PDF dictionary.");
+
+ Stream = new(value, this);
// Always set the length.
Elements["/Length"] = new PdfInteger(Stream.Length);
return Stream;
}
+ ///
+ /// Clones the elements of the specified PDF dictionary.
+ ///
+ internal void CloneElementsOf(PdfDictionary dic)
+ {
+ _elements = dic.Elements.Clone();
+ // Note that ParentInfo is still null for each item.
+ _elements.OwningContainer = this;
+ }
+
///
/// When overridden in a derived class, gets the KeysMeta of this dictionary type.
///
internal virtual DictionaryMeta Meta => null!;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ new void EnsureAlive()
+ {
+ if (IsDead)
+ {
+ throw new InvalidOperationException(
+ "This dictionary cannot be used anymore, because its content is now owned by an object of a derived class.");
+ }
+ }
+
///
/// Represents the interface to the elements of a PDF dictionary.
///
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
- public sealed class DictionaryElements : IDictionary, ICloneable
+ public sealed class DictionaryElements : ElementsBase, IDictionary, ICloneable
{
- internal DictionaryElements(PdfDictionary ownerDictionary)
+ // Note that the value type of IDictionary is not nullable anymore.
+ // This is because the value of a dictionary element is always a PdfItem and cannot be null.
+ // However, the index still returns PdfItem? because it returns null if the key does not exist.
+
+ internal DictionaryElements(PdfDictionary owningDictionary)
+ : base(owningDictionary)
{
- _elements = new Dictionary();
- _ownerDictionary = ownerDictionary;
+ Debug.Assert(ReferenceEquals(OwningDictionary, owningDictionary));
}
object ICloneable.Clone()
{
+ // Shallow clone the Dictionary elements.
var dictionaryElements = (DictionaryElements)MemberwiseClone();
- dictionaryElements._elements = new Dictionary(dictionaryElements._elements);
- dictionaryElements._ownerDictionary = null!;
+ var elements = new Dictionary(_elements);
+ dictionaryElements._elements = elements;
+ dictionaryElements.OwningContainer = null!;
+
+ // Clone all direct objects.
+ var names = _elements.Keys; // Take original dictionary…
+ foreach (var name in names)
+ {
+ var item = elements[name];
+ if (item is PdfObject obj)
+ {
+ Debug.Assert(obj.IsIndirect is false);
+ Debug.Assert(obj.ParentInfo is not null);
+ obj = obj.Clone();
+ Debug.Assert(obj.ParentInfo is null);
+ obj.SetStructureParent(dictionaryElements, name);
+ elements[name] = obj; // … because we change the clone during iteration.
+ }
+ else if (item is PdfReference reference)
+ {
+ reference.AddRef();
+ }
+ else
+ {
+ // Nothing to do for PDF primitives.
+ //_ = typeof(int);
+ }
+ }
+
return dictionaryElements;
}
@@ -280,365 +365,738 @@ public DictionaryElements Clone()
///
/// Moves this instance to another dictionary during object type transformation.
///
- internal void ChangeOwner(PdfDictionary ownerDictionary)
+ internal void ChangeOwner(PdfDictionary newOwningDictionary)
{
- if (_ownerDictionary != null!)
+ if (OwningDictionary != null!)
{
- // ???
+ // Can this assertion really fail?
+ Debug.Assert(ReferenceEquals(this, OwningDictionary._elements)); //TODO: Check why it fails.
+
+ // Disconnect old owner from this DictionaryElements.
+ OwningDictionary._elements = null;
}
// Set new owner.
- _ownerDictionary = ownerDictionary;
+ OwningContainer = newOwningDictionary;
// Set owners elements to this.
- ownerDictionary._elements = this;
+ newOwningDictionary._elements = this;
}
///
- /// Gets the dictionary to which this elements object belongs to.
+ /// Determines whether the specified key has a value.
///
- internal PdfDictionary Owner => _ownerDictionary ?? NRT.ThrowOnNull();
+ public bool HasValue(string key)
+ => _elements.ContainsKey(key);
+
+ // ===== PdfBoolean =====
///
- /// Converts the specified value to boolean.
- /// If the value does not exist, the function returns false.
- /// If the value is not convertible, the function throws an InvalidCastException.
+ /// Gets the boolean value that corresponds to the specified key.
+ /// If the key does not exist and create is false, the function returns the defaultValue.
+ /// If the key does not exist and create is true,
+ /// a direct value will be created using the defaultValue.
+ /// If the key exists but the value is neither a boolean nor a PDF reference to a boolean,
+ /// the function throws an InvalidOperationException.
+ ///
+ public bool GetBoolean(string key, bool create = false, bool defaultValue = false)
+ {
+ TryGetBooleanInternal(key, out var result, create, defaultValue, true);
+ return result;
+ }
+
+ ///
+ /// Tries to get the boolean value of the PDF object with the specified key.
+ /// Returns true on success, false otherwise.
///
- public bool GetBoolean(string key, bool create)
+ public bool TryGetBoolean(string key, out bool result)
+ => TryGetBooleanInternal(key, out result, false, false, false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetBooleanInternal(string key, out bool result, bool create, bool defaultValue,
+ bool throwOnTypeMismatch)
{
- object? obj = this[key];
- if (obj == null)
+ Name.EnsureName(key);
+ //var value = this[key];
+ var value = GetValue(key); // #US373
+ if (value == null)
{
+ result = defaultValue;
if (create)
- this[key] = new PdfBoolean();
+ {
+ SetValueInternal(key, new PdfBoolean(defaultValue));
+ return true;
+ }
return false;
}
- //if (obj is PdfReference reference)
- // obj = reference.Value;
- PdfReference.Dereference(ref obj);
-
- return obj switch
+ // PdfReference.Dereference(ref value); // #US373
+ bool success = true;
+ result = value switch
{
PdfBoolean boolean => boolean.Value,
- PdfBooleanObject booleanObject => booleanObject.Value,
- _ => throw new InvalidCastException("GetBoolean: Object is not a boolean.")
+ PdfBooleanObject boolean => boolean.Value,
+ _ => Fail()
};
- }
+ return success;
- ///
- /// Converts the specified value to boolean.
- /// If the value does not exist, the function returns false.
- /// If the value is not convertible, the function throws an InvalidCastException.
- ///
- public bool GetBoolean(string key)
- => GetBoolean(key, false);
+ bool Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfBoolean), value.GetType()).Message);
+ }
+
+ success = false;
+ return defaultValue;
+ }
+ }
///
/// Sets the entry to a direct boolean value.
///
public void SetBoolean(string key, bool value)
- => this[key] = new PdfBoolean(value);
+ => SetValueInternal(key, new PdfBoolean(value));
+
+ // ===== PdfInteger =====
///
- /// Converts the specified value to integer.
- /// If the value does not exist, the function returns 0.
- /// If the value is not convertible, the function throws an InvalidCastException.
+ /// Gets the integer value that corresponds to the specified key.
+ /// If the key does not exist and create is false, the function returns the defaultValue.
+ /// If the key does not exist and create is true,
+ /// a direct value will be created using the defaultValue.
+ /// If the key exists but the value is neither an integer nor a PDF reference to an integer,
+ /// the function throws an InvalidOperationException.
///
- public int GetInteger(string key, bool create)
+ public int GetInteger(string key, bool create = false, int defaultValue = 0)
{
- object? obj = this[key];
- if (obj == null)
- {
- if (create)
- this[key] = new PdfInteger();
- return 0;
- }
-
- if (obj is PdfNull)
- return 0;
-
- if (obj is PdfReference reference)
- obj = reference.Value;
-
- return obj switch
- {
- PdfInteger integer => integer.Value,
- PdfIntegerObject integerObject => integerObject.Value,
- _ => throw new InvalidCastException("GetInteger: Object is not an integer.")
- };
+ TryGetIntegerInternal(key, out int result, create, defaultValue, true);
+ return result;
}
///
- /// Converts the specified value to integer.
- /// If the value does not exist, the function returns 0.
- /// If the value is not convertible, the function throws an InvalidCastException.
+ /// Tries to get the integer value for the specified key.
+ /// Returns true on success, false if value is of wrong type.
///
- public int GetInteger(string key)
- => GetInteger(key, false);
+ public bool TryGetInteger(string key, out int result)
+ => TryGetIntegerInternal(key, out result, false, 0, false);
- ///
- /// Converts the specified value to unsigned integer.
- /// If the value does not exist, the function returns 0.
- /// If the value is not convertible, the function throws an InvalidCastException.
- ///
- public uint GetUnsignedInteger(string key, bool create)
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetIntegerInternal(string key, out int result, bool create, int defaultValue,
+ bool throwOnTypeMismatch)
{
- object? obj = this[key];
- if (obj == null)
+ Name.EnsureName(key);
+ //var value = this[key];
+ var value = GetValue(key, VCF.NoTransform); // #US373
+ if (value == null)
{
+ result = defaultValue;
if (create)
- this[key] = new PdfInteger();
- return 0;
+ {
+ SetValueInternal(key, new PdfInteger(defaultValue));
+ return true;
+ }
+ return false;
}
- if (obj is PdfNull)
- return 0;
+ // PdfReference.Dereference(ref value); // #US373
+ bool success = true;
+ result = value switch
+ {
+ PdfInteger integer => integer.Value,
+ PdfIntegerObject integer => integer.Value,
+ PdfLongInteger integer => LongInteger(integer.Value),
+ PdfLongIntegerObject integer => LongInteger(integer.Value),
+ _ => Fail()
+ };
+ return success;
- if (obj is PdfReference reference)
- obj = reference.Value;
+ int LongInteger(long l)
+ {
+ if (l is >= Int32.MinValue and <= Int32.MaxValue)
+ return (int)l;
+ return Fail();
+ }
- return obj switch
+ int Fail()
{
- PdfInteger integer => (uint)integer.Value,
- PdfIntegerObject integerObject => (uint)integerObject.Value,
- PdfLongInteger longInteger => longInteger.Value is >= 0 and <= uint.MaxValue ? (uint)longInteger.Value : throw new InvalidCastException("GetUnsignedInteger: Long integer object is not an integer."),
- _ => throw new InvalidCastException("GetUnsignedInteger: Object is not an integer.")
- };
- }
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfInteger), value.GetType()).Message);
+ }
- ///
- /// Converts the specified value to integer.
- /// If the value does not exist, the function returns 0.
- /// If the value is not convertible, the function throws an InvalidCastException.
- ///
- public uint GetUnsignedInteger(string key)
- => GetUnsignedInteger(key, false);
+ success = false;
+ return defaultValue;
+ }
+ }
///
/// Sets the entry to a direct integer value.
///
public void SetInteger(string key, int value)
- => this[key] = new PdfInteger(value);
+ => SetValueInternal(key, new PdfInteger(value));
///
- /// Converts the specified value to double.
- /// If the value does not exist, the function returns 0.
- /// If the value is not convertible, the function throws an InvalidCastException.
+ /// Sets the entry to a direct integer value that is used as a flag.
///
- public double GetReal(string key, bool create)
- {
- object? obj = this[key];
- if (obj == null)
- {
- if (create)
- this[key] = new PdfReal();
- return 0;
- }
-
- if (obj is PdfReference reference)
- obj = reference.Value;
+ public void SetIntegerFlag(string key, int value)
+ => SetValueInternal(key, new PdfInteger(value, true));
- return obj switch
- {
- PdfReal real => real.Value,
- PdfRealObject realObject => realObject.Value,
- PdfInteger integer => integer.Value,
- PdfIntegerObject integerObject => integerObject.Value,
- _ => throw new InvalidCastException("GetReal: Object is not a number.")
- };
- }
+ // ===== PdfInteger as unsigned integer =====
///
- /// Converts the specified value to double.
- /// If the value does not exist, the function returns 0.
- /// If the value is not convertible, the function throws an InvalidCastException.
+ /// Gets the unsigned integer value that corresponds to the specified key.
+ /// If the key does not exist and create is false, the function returns the defaultValue.
+ /// If the key does not exist and create is true,
+ /// a direct value will be created using the defaultValue.
+ /// If the key exists but the value is neither an integer nor a PDF reference to an integer,
+ /// the function throws an InvalidOperationException.
///
- public double GetReal(string key)
- => GetReal(key, false);
+ public uint GetUnsignedInteger(string key, bool create = false, uint defaultValue = 0) // TOD
+ {
+ TryGetUnsignedIntegerInternal(key, out uint result, create, defaultValue, true);
+ return result;
+ }
///
- /// Sets the entry to a direct double value.
+ /// Tries to get the unsigned integer value for the specified key.
+ /// Returns true on success, false if value is of wrong type.
///
- public void SetReal(string key, double value)
- => this[key] = new PdfReal(value);
+ public bool TryGetUnsignedInteger(string key, out uint result)
+#if true
+ => TryGetUnsignedIntegerInternal(key, out result, false, 0, false);
+#else
+ {
+ bool success = TryGetLongIntegerInternal(key, out long longResult, false, 0, false);
+ if (!success || longResult < Int32.MinValue || longResult > UInt32.MaxValue)
+ {
+ result = 0;
+ return false;
+ }
- ///
- /// Converts the specified value to String.
- /// If the value does not exist, the function returns the empty string.
- ///
- public string GetString(string key, bool create)
+ result = unchecked((uint)longResult);
+ return true;
+ }
+#endif
+ bool TryGetUnsignedIntegerInternal(string key, out uint result, bool create, uint defaultValue, bool throwOnTypeMismatch)
{
- object? obj = this[key];
- if (obj == null)
+ // Background: PDF treats flags (like permissions) as a 32-bit unsigned integer.
+ // If the most significant bit is set, PDFsharp writes it as a negative integer.
+ // But some producer apps write it as a positive value in the range [int.MaxValue+1..uint.MaxValue].
+ // PDFsharp parses such a number as PdfLongInteger.
+ // This code handles that case.
+
+ Name.EnsureName(key);
+ var value = this[key]; // #US373 Also use GetValue here?
+ if (value == null)
{
+ result = defaultValue;
if (create)
- this[key] = new PdfString();
- return "";
- }
+ {
+ SetValueInternal(key, new PdfInteger(unchecked((int)defaultValue)));
+ return true;
+ }
- if (obj is PdfReference reference)
- obj = reference.Value;
+ return false;
+ }
- return obj switch
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- PdfString str => str.Value,
- PdfStringObject strObject => strObject.Value,
- PdfName name => name.Value,
- PdfNameObject nameObject => nameObject.Value,
- _ => throw new InvalidCastException("GetString: Object is not a string.")
+ PdfInteger integer => FromInteger(integer.Value),
+ PdfIntegerObject integer => FromInteger(integer.Value),
+ PdfLongInteger longInteger => FromLongInteger(longInteger.Value),
+ PdfLongIntegerObject longInteger => FromLongInteger(longInteger.Value),
+ _ => Fail()
};
- }
+ return success;
- ///
- /// Converts the specified value to String.
- /// If the value does not exist, the function returns the empty string.
- ///
- public string GetString(string key)
- => GetString(key, false);
-
- ///
- /// Tries to get the string. TODO_OLD: more TryGet...
- ///
- public bool TryGetString(string key, [MaybeNullWhen(false)] out string value)
- {
- value = null;
- var obj = this[key];
- if (obj == null)
- return false;
+ uint FromInteger(int value)
+ {
+ return unchecked((uint)value);
+ }
- if (obj is PdfReference reference)
- obj = reference.Value;
+ uint FromLongInteger(long value)
+ {
+ // if (value is >= 0 and <= UInt32.MaxValue)
+ if (value is >= Int32.MinValue and <= UInt32.MaxValue)
+ return unchecked((uint)value);
+ return Fail();
+ }
- switch (obj)
+ uint Fail()
{
- case PdfString str:
- value = str.Value;
- return true;
- case PdfStringObject strObject:
- value = strObject.Value;
- return true;
- case PdfName name:
- value = name.Value;
- return true;
- case PdfNameObject nameObject:
- value = nameObject.Value;
- return true;
- default:
- return false;
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfInteger), value.GetType()).Message);
+ }
+
+ success = false;
+ return defaultValue;
}
}
+ // ===== PdfLongInteger =====
+
///
- /// Sets the entry to a direct string value.
+ /// Gets the integer or long integer value that corresponds to the specified key.
+ /// If the key does not exist and create is false, the function returns the defaultValue.
+ /// If the key does not exist and create is true,
+ /// a direct value will be created using the defaultValue.
+ /// If the key exists but the value is neither an integer or a long integer
+ /// nor a PDF reference to an integer or a long integer,
+ /// the function throws an InvalidOperationException.
///
- public void SetString(string key, string value)
- => this[key] = new PdfString(value);
+ public long GetLongInteger(string key, bool create = false, long defaultValue = 0)
+ {
+ TryGetLongIntegerInternal(key, out long result, create, defaultValue, true);
+ return result;
+ }
///
- /// Converts the specified value to a name.
- /// If the value does not exist, the function returns the empty string.
+ /// Tries to get the long integer value for the specified key.
+ /// Returns true on success, false if value is of wrong type.
///
- public string GetName(string key)
+ public bool TryGetLongInteger(string key, out long result)
+ => TryGetLongIntegerInternal(key, out result, false, 0, false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetLongIntegerInternal(string key, out long result, bool create, long defaultValue,
+ bool throwOnTypeMismatch)
{
- var obj = this[key];
- if (obj == null)
+ Name.EnsureName(key);
+ var value = this[key];
+ if (value == null)
{
- //if (create)
- // this[key] = new Pdf();
- return "";
- }
+ result = defaultValue;
+ if (create)
+ {
+ SetValueInternal(key, new PdfLongInteger(defaultValue));
+ return true;
+ }
- if (obj is PdfReference reference)
- obj = reference.Value;
+ return false;
+ }
- return obj switch
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- PdfName name => name.Value,
- PdfNameObject nameObject => nameObject.Value,
- _ => throw new InvalidCastException("GetName: Object is not a name.")
+ PdfInteger integer => integer.Value,
+ PdfIntegerObject integer => integer.Value,
+ PdfLongInteger longInteger => longInteger.Value,
+ PdfLongIntegerObject longInteger => longInteger.Value,
+ _ => Fail()
};
+ return success;
+
+ long Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfLongInteger), value.GetType()).Message);
+ }
+
+ success = false;
+ return defaultValue;
+ }
}
///
- /// Sets the specified name value.
- /// If the value doesn’t start with a slash, it is added automatically.
+ /// Sets the entry to a direct long integer value.
///
- public void SetName(string key, string value)
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
+ public void SetLongInteger(string key, long value)
+ => SetValueInternal(key, new PdfLongInteger(value));
- if (value.Length == 0 || value[0] != '/')
- value = "/" + value;
+ // ===== PdfBoolean =====
- this[key] = new PdfName(value);
+ ///
+ /// Gets the real or integer value that corresponds to the specified key.
+ /// If the key does not exist and create is false, the function returns the defaultValue.
+ /// If the key does not exist and create is true,
+ /// a direct value will be created using the defaultValue.
+ /// If the key exists but the value is neither a real or an integer
+ /// nor a PDF reference to a real or an integer, the function throws an InvalidOperationException.
+ ///
+ public double GetReal(string key, bool create = false, double defaultValue = 0.0)
+ {
+ TryGetRealInternal(key, out double result, create, defaultValue, true);
+ return result;
}
///
- /// Converts the specified value to PdfRectangle.
- /// If the value does not exist, the function returns an empty rectangle.
- /// If the value is not convertible, the function throws an InvalidCastException.
+ /// Tries to get the real value for the specified key.
+ /// Returns true on success, false if value is of wrong type.
///
- public PdfRectangle GetRectangle(string key, bool create)
+ public bool TryGetReal(string key, out double result)
+ => TryGetRealInternal(key, out result, false, 0, false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetRealInternal(string key, out double result, bool create, double defaultValue,
+ bool throwOnTypeMismatch)
{
- var obj = this[key];
- if (obj == null)
+ Name.EnsureName(key);
+ var value = this[key];
+ if (value == null)
{
+ result = defaultValue;
if (create)
- return (PdfRectangle)(this[key] = new PdfRectangle());
- return new();
+ {
+ SetValueInternal(key, new PdfReal(defaultValue));
+ return true;
+ }
+
+ return false;
}
- if (obj is PdfReference reference)
- obj = reference.Value;
- if (obj is PdfArray { Elements.Count: 4 } array)
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- return (PdfRectangle)(this[key] =
- new PdfRectangle(array.Elements.GetReal(0), array.Elements.GetReal(1),
- array.Elements.GetReal(2), array.Elements.GetReal(3)));
- }
+ PdfReal real => real.Value,
+ PdfRealObject realObject => realObject.Value,
+ PdfInteger integer => integer.Value,
+ PdfIntegerObject integer => integer.Value,
+ PdfLongInteger longInteger => longInteger.Value,
+ PdfLongIntegerObject longInteger => longInteger.Value,
+ _ => Fail()
+ };
+ return success;
- if (obj is PdfRectangle rectangle)
- return rectangle;
+ double Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfReal), value.GetType()).Message);
+ }
- throw new InvalidOperationException($"PDF item is '{obj.GetType().FullName}', but PdfRectangle expected.");
+ success = false;
+ return defaultValue;
+ }
}
///
- /// Converts the specified value to PdfRectangle.
- /// If the value does not exist, the function returns an empty rectangle.
- /// If the value is not convertible, the function throws an InvalidCastException.
+ /// Sets the entry to a direct double value.
+ ///
+ public void SetReal(string key, double value)
+ => SetValueInternal(key, new PdfReal(value));
+
+ // No SetNullableReal because there is no use case.
+
+ // ===== PdfString =====
+
+ ///
+ /// Gets the string value that corresponds to the specified key.
+ /// If the key does not exist and create is false, the function returns the defaultValue.
+ /// If the key does not exist and create is true,
+ /// a direct value will be created using the defaultValue.
+ /// If the key exists but the value is neither a string or name
+ /// nor a PDF reference to a string or a name, the function throws an InvalidOperationException.
+ ///
+ public string GetString(string key, bool create = false, string defaultValue = "")
+ {
+ TryGetStringInternal(key, out string result, create, defaultValue, true);
+ return result;
+ }
+
+ ///
+ /// Tries to get the name value for the specified key.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetString(string key, [MaybeNullWhen(false)] out string result)
+ => TryGetStringInternal(key, out result, false, "", false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetStringInternal(string key, out string result, bool create,
+ string defaultValue, bool throwOnTypeMismatch)
+ {
+ Name.EnsureName(key);
+ var value = this[key];
+ if (value == null)
+ {
+ result = defaultValue;
+ if (create)
+ {
+ SetValueInternal(key, new PdfString(defaultValue));
+ return true;
+ }
+
+ return false;
+ }
+
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
+ {
+ PdfString str => str.Value,
+ PdfStringObject strObject => strObject.Value,
+ PdfName name => name.Value,
+ PdfNameObject nameObject => nameObject.Value,
+ _ => Fail()
+ };
+ return success;
+
+ string Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfString), value.GetType()).Message);
+ }
+
+ success = false;
+ return defaultValue;
+ }
+ }
+
+ ///
+ /// Sets the entry to a direct string value.
+ ///
+ public void SetString(string key, string value)
+ => SetValueInternal(key, new PdfString(value));
+
+ // ===== PdfName =====
+
+ ///
+ /// Gets the name value that corresponds to the specified key.
+ /// If the key does not exist and create is false, the function returns the defaultValue.
+ /// If the key does not exist and create is true,
+ /// a direct value will be created using the defaultValue.
+ /// If the key exists but the value is neither a name nor a PDF reference to a name,
+ /// the function throws an InvalidOperationException.
+ /// Note that the default defaultValue is the empty name ("/"), not the empty string.
+ ///
+ public string GetName(string key, bool create = false, string defaultValue = "/")
+ {
+ TryGetNameInternal(key, out string result, create, defaultValue, true);
+ return result;
+ }
+
+ ///
+ /// Tries to get the name value for the specified key.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetName(string key, out string result)
+ => TryGetNameInternal(key, out result, false, "/", false);
+
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetNameInternal(string key, out string result, bool create,
+ string defaultValue, bool throwOnTypeMismatch)
+ {
+ Name.EnsureName(key);
+ var value = this[key];
+ if (value == null)
+ {
+ // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
+ result = defaultValue ?? "/";
+ if (create)
+ {
+ SetValueInternal(key, new PdfName(result)); // Fail in Name if defaultValue is empty.
+ return true;
+ }
+
+ return false;
+ }
+
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
+ {
+ PdfName name => name.Value,
+ PdfNameObject name => name.Value,
+ _ => Fail()
+ };
+ return success;
+
+ string Fail()
+ {
+ if (throwOnTypeMismatch)
+ {
+ throw new InvalidOperationException(
+ SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfName), value.GetType()).Message);
+ }
+
+ success = false;
+ // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
+ return defaultValue ?? "/";
+ }
+ }
+
+ ///
+ /// Sets a PDF name at the specified key.
+ /// If the value doesn’t start with a slash, it is added automatically,
+ /// but starting with PDFsharp version 6.3 a warning is logged.
+ ///
+ public void SetName(string key, string value)
+ {
+ Name.EnsureName(key);
+
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ // Ensure that value already starts with a slash.
+ if (value.Length == 0 || value[0] != '/')
+ {
+ PdfSharpLogHost.Logger.LogWarning("A PDF name must start with a '/'.");
+ value = String.Concat("/", value);
+ }
+
+ SetValueInternal(key, new PdfName(value));
+ }
+
+ public void SetName(string key, T value) where T : Enum
+ {
+ SetName(key, Name.FromEnum(value).Value);
+ }
+
+ // ===== PdfRectangle =====
+
+ ///
+ /// Gets the rectangle value that corresponds to the specified key.
+ /// If the key does not exist and create is false, the function returns the defaultValue.
+ /// If the key does not exist and create is true,
+ /// a direct value will be created using the defaultValue.
+ /// If the key exists but the value is neither a rectangle or an array with four elements
+ /// nor a PDF reference to a rectangle or an array with four elements,
+ /// the function throws an InvalidOperationException.
+ ///
+ public PdfRectangle? GetRectangle(string key, bool create = false, PdfRectangle? defaultValue = null)
+ {
+ TryGetRectangleInternal(key, out var result, create, defaultValue, true);
+ return result;
+ }
+
+ public PdfRectangle GetRequiredRectangle(string key, bool create = false, PdfRectangle? defaultValue = null)
+ {
+ if (create && defaultValue == null)
+ throw new InvalidOperationException("Cannot create a rectangle if the default value is null.");
+
+ if (TryGetRectangleInternal(key, out var result, create, defaultValue, true)
+ || result != null)
+ return result;
+ throw ExceptionOnNull(key);
+ }
+
+ ///
+ /// Tries to get the rectangle value for the specified key.
+ /// Returns true on success, false if value is of wrong type.
///
- public PdfRectangle GetRectangle(string key)
- => GetRectangle(key, false);
+ public bool TryGetRectangle(string key, [MaybeNullWhen(false)] out PdfRectangle result)
+ => TryGetRectangleInternal(key, out result, false, null, false);
+
+ // Re/Sharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetRectangleInternal(string key, [MaybeNullWhen(false)] out PdfRectangle result, bool create,
+ PdfRectangle? defaultValue, bool throwOnTypeMismatch)
+ {
+ Name.EnsureName(key);
+ var value = this[key];
+ if (value == null)
+ {
+ result = defaultValue;
+ if (create)
+ {
+ if (result == null)
+ throw new InvalidOperationException("You cannot create a PdfRectangle if the default value is null.");
+ SetValueInternal(key, result);
+ return true;
+ }
+ return false;
+ }
+
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
+ {
+ // PdfRectangle is replaced by PdfArray in SetValueInternal.
+ PdfRectangle rect => throw new InvalidOperationException("Should not come here anymore"),
+ PdfArray array => FromArray(array),
+ _ => Fail()
+ };
+ return success;
+
+ //PdfRectangle FromRectangle(PdfRectangle rc) // DELETE
+ //{
+ // // PdfRectangle objects should be replaced by PdfArray.
+ // throw new InvalidOperationException("Should not come here anymore");
+ //}
+
+ PdfRectangle? FromArray(PdfArray array)
+ {
+ if (array.Elements.Count == 4)
+ {
+ var rectangle = new PdfRectangle(array.Elements.GetReal(0), array.Elements.GetReal(1),
+ array.Elements.GetReal(2), array.Elements.GetReal(3));
+
+ return rectangle;
+ }
+ return Fail("A PdfRectangle expects a PdfArray with 4 real values.");
+ }
+
+ PdfRectangle? Fail(string? message = "")
+ {
+ if (throwOnTypeMismatch)
+ {
+ message ??= SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfArray), value.GetType()).Message;
+ throw new InvalidOperationException(message);
+ }
+ success = false;
+ return defaultValue; //?? new PdfRectangle(); TODO test
+ }
+ }
///
/// Sets the entry to a direct rectangle value, represented by an array with four values.
///
public void SetRectangle(string key, PdfRectangle rect)
- => _elements[key] = rect;
+ {
+ // Setting PdfRectangle is handled as a special case in SetValueInternal.
+ // This is because dict.SetValue(key, rect) also must be handled correctly.
+ SetValueInternal(key, rect);
+ }
+
+ // ===== XMatrix =====
+ // IMPROVE: Should be extension method to separate XGraphics from PDF Core.
/// Converts the specified value to XMatrix.
/// If the value does not exist, the function returns an identity matrix.
/// If the value is not convertible, the function throws an InvalidCastException.
- public XMatrix GetMatrix(string key, bool create)
+ public XMatrix GetMatrix(string key, bool create = false, XMatrix? defaultMatrix = null)
{
- var obj = this[key];
- if (obj == null)
+ //var item = this[key];
+ var item = GetValue(key); // #US373
+ if (item == null)
{
if (create)
- this[key] = new PdfLiteral("[1 0 0 1 0 0]"); // cannot be parsed, implement a PdfMatrix...
- return XMatrix.Identity;
+ {
+ PdfArray array;
+ if (defaultMatrix is null)
+ {
+ array = new(new PdfInteger(1), new PdfInteger(0),
+ new PdfInteger(0), new PdfInteger(1),
+ new PdfInteger(0), new PdfInteger(0));
+ }
+ else
+ {
+ array = new(new PdfReal(defaultMatrix.Value.M11), new PdfReal(defaultMatrix.Value.M12),
+ new PdfReal(defaultMatrix.Value.M21), new PdfReal(defaultMatrix.Value.M22),
+ new PdfReal(defaultMatrix.Value.OffsetX), new PdfReal(defaultMatrix.Value.OffsetY));
+ }
+ SetValueInternal(key, array);
+ }
+ return defaultMatrix ?? XMatrix.Identity;
}
- if (obj is PdfReference reference)
- obj = reference.Value;
-
- return obj switch
+ //PdfReference.Dereference(ref item);
+ return item switch
{
PdfArray { Elements.Count: 6 } array =>
- new(array.Elements.GetReal(0), array.Elements.GetReal(1),
+ new(array.Elements.GetReal(0), array.Elements.GetReal(1),
array.Elements.GetReal(2), array.Elements.GetReal(3),
array.Elements.GetReal(4), array.Elements.GetReal(5)),
PdfLiteral => throw new NotImplementedException("Parsing matrix from literal."),
@@ -646,499 +1104,1138 @@ public XMatrix GetMatrix(string key, bool create)
};
}
- /// Converts the specified value to XMatrix.
- /// If the value does not exist, the function returns an identity matrix.
- /// If the value is not convertible, the function throws an InvalidCastException.
- public XMatrix GetMatrix(string key)
- => GetMatrix(key, false);
+ //public bool TryGetMatrix(string key, out XMatrix result)
+ //{
+ // //TODO?
+ // throw new NotImplementedException();
+ //}
+
+ //public bool TryGetMatrixInternal(string key, out XMatrix result)
+ //{
+ // //TODO?
+ // throw new NotImplementedException();
+ //}
///
/// Sets the entry to a direct matrix value, represented by an array with six values.
///
public void SetMatrix(string key, XMatrix matrix)
- => _elements[key] = PdfLiteral.FromMatrix(matrix);
+ {
+ SetValueInternal(key, new PdfArray(
+ new PdfReal(matrix.M11), new PdfReal(matrix.M12),
+ new PdfReal(matrix.M21), new PdfReal(matrix.M22),
+ new PdfReal(matrix.OffsetX), new PdfReal(matrix.OffsetY)));
+ }
+
+ // ===== PdfDate =====
///
- /// Converts the specified value to DateTime.
+ /// Converts the specified value to DateTimeOffset.
/// If the value does not exist, the function returns the specified default value.
- /// If the value is not convertible, the function throws an InvalidCastException.
+ /// If the value is not convertible, the function throws an InvalidOperationException.
///
- public DateTime GetDateTime(string key, DateTime defaultValue)
+ public DateTimeOffset? GetDateTime(string key, DateTimeOffset? defaultValue = null, bool create = false)
{
- var obj = this[key];
- if (obj == null)
- return defaultValue;
+ TryGetDateTimeInternal(key, out DateTimeOffset? result, create, defaultValue, true);
+ return result;
+ }
- //PdfReference.Dereference(ref obj);
- if (obj is PdfReference reference)
- obj = reference.Value;
+ public DateTimeOffset GetRequiredDateTime(string key, DateTimeOffset? defaultValue = null, bool create = false)
+ {
+ TryGetDateTimeInternal(key, out DateTimeOffset? result, create, defaultValue, true);
+ if (result == null)
+ throw ExceptionOnNull(key);
+ return result.Value;
+ }
- if (obj is PdfDate date)
- return date.Value;
+ ///
+ /// Tries to get the date value for the specified key.
+ /// Returns true on success, false if value is of wrong type.
+ ///
+ public bool TryGetDateTime(string key, out DateTimeOffset? result)
+ {
+ return TryGetDateTimeInternal(key, out result, false, null, false);
+ }
- string strDate;
- if (obj is PdfString pdfString)
- strDate = pdfString.Value;
- else
+ // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
+ bool TryGetDateTimeInternal(string key, [MaybeNullWhen(false)] out DateTimeOffset? result, bool create,
+ DateTimeOffset? defaultValue, bool throwOnTypeMismatch)
+ {
+ Name.EnsureName(key);
+
+ result = null;
+ var value = this[key];
+ if (value == null)
{
- if (obj is PdfStringObject stringObject)
- strDate = stringObject.Value;
- else
- throw new InvalidCastException("GetName: Object is not a name.");
+ result = defaultValue;
+ if (create)
+ {
+ if (result == null)
+ {
+ // Cannot create value without default value.
+ return false;
+ }
+ SetValueInternal(key, new PdfDate(result.Value));
+ return true;
+ }
+ return false;
}
- if (strDate != "")
+ PdfReference.Dereference(ref value);
+ bool success = true;
+ result = value switch
{
- try
+ PdfDate date => date.Value,
+ PdfString date => DateFromString(date.Value),
+ PdfStringObject date => DateFromString(date.Value),
+ _ => Fail()
+ };
+ return success;
+
+ DateTimeOffset? DateFromString(string pdfDate)
+ {
+ if (Parser.TryParseDate(pdfDate, out var dateTime))
+ return dateTime;
+ // TODO Throw exception here, indicating malformed date string?
+ return Fail();
+ }
+
+ DateTimeOffset? Fail()
+ {
+ if (throwOnTypeMismatch)
{
- defaultValue = Parser.ParseDateTime(strDate, defaultValue);
+ throw new InvalidOperationException(
+ SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfDate), value.GetType()).Message);
}
- // ReSharper disable EmptyGeneralCatchClause
- catch { }
- // ReSharper restore EmptyGeneralCatchClause
+
+ success = false;
+ return defaultValue;
}
- return defaultValue;
}
///
/// Sets the entry to a direct datetime value.
///
+ [Obsolete("Use DateTimeOffset as parameter.")]
public void SetDateTime(string key, DateTime value)
- => _elements[key] = new PdfDate(value);
+ {
+ Name.EnsureName(key);
+
+ SetValueInternal(key, new PdfDate(value));
+ }
+
+ ///
+ /// Sets the entry to a direct datetime value.
+ ///
+ public void SetDateTime(string key, DateTimeOffset value)
+ {
+ Name.EnsureName(key);
+
+ SetValueInternal(key, new PdfDate(value));
+ }
+
+ // ===== GetValue =====
+
+ ///
+ /// Gets the value for the specified key.
+ /// If the value does not exist, it is optionally created.
+ /// If the value is a PDF reference the referenced object is returned, except the itemType
+ /// is set to PdfReference.
+ ///
+ public PdfItem? GetValue(string key, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+#if PDFSHARP_DEBUG_
+ if (ShouldBreak5)
+ Debugger.Break();
+#endif
+ Name.EnsureName(key);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ return GetValueInternal(key, options, valueType, valueType != null);
+ }
+
+ ///
+ /// Gets the result as GetValue if it is not null.
+ /// Otherwise, an exception is thrown.
+ ///
+ public PdfItem GetRequiredValue(string key, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ return GetValue(key, options, valueType)
+ ?? throw ExceptionOnNull(key);
+ }
+
+ // For interface IDictionary.TryGetValue(string, out PdfItem)
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out PdfItem value)
+ => TryGetValue(key, out value, null);
+
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out PdfItem value,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType)
+ {
+ Name.EnsureName(key);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ value = null;
+ var result = GetValueInternal(key, VCF.NoTransform, valueType, false);
+ if (result != null)
+ {
+ value = result;
+ return true;
+ }
+
+ return false;
+ }
+
+ public T? GetValue<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, VCF options = VCF.None) where T : PdfItem
+ {
+ Name.EnsureName(key);
+
+ var value = GetValueInternal(key, options, typeof(T), true) as T;
+ return value;
+ }
+
+ public T GetRequiredValue<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, VCF options = VCF.None) where T : PdfItem
+ {
+ return GetValue(key, options)
+ ?? throw ExceptionOnNull(key);
+ }
+
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out T value) where T : PdfItem
+ {
+ Name.EnsureName(key);
+
+ value = null;
+ if (GetValueInternal(key, VCF.NoTransform, null, false) is T valueOfT)
+ {
+ value = valueOfT;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Sets the entry with the specified value. DON’T USE THIS FUNCTION - IT MAY BE REMOVED. // PDFsharp/NT
+ ///
+ public void SetValue(string key, PdfItem value)
+ {
+ Name.EnsureName(key);
+
+ SetValueInternal(key, value); // TODO: Check for indirect objects.
+ }
+
+ // ===== GetObject =====
+
+ ///
+ /// Gets the PdfObject with the specified key, or null if no such object exists.
+ /// If the key refers to a reference, the referenced PdfObject is returned.
+ /// *
+ ///
+ public PdfObject? GetObject(string key, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ Name.EnsureName(key);
+ if (valueType != null)
+ EnsureValueType(valueType);
+ //else
+ // valueType = typeof(PdfObject);
+
+ var value = GetValueInternal(key, options, valueType, false);
+ return value as PdfObject;
+ }
+
+ public PdfObject GetRequiredObject(string key, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ return GetObject(key, options, valueType)
+ ?? throw ExceptionOnNull(key);
+ }
+
+ public T? GetObject<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, VCF options = VCF.None) where T : PdfObject
+ {
+ Name.EnsureName(key);
+
+ // TODO: Never throw?
+ bool throwOnTypeMismatch = typeof(PdfContainer).IsAssignableFrom(typeof(T));
+ return GetValueInternal(key, options, typeof(T), throwOnTypeMismatch) as T;
+ }
+
+ public T GetRequiredObject<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, VCF options = VCF.None) where T : PdfObject
+ {
+ return GetObject(key, options)
+ ?? throw ExceptionOnNull(key);
+ }
+
+ ///
+ /// Sets the entry to the specified object.
+ ///
+ public void SetObject(string key, PdfObject obj) // Used in PDFsharp
+ {
+ Name.EnsureName(key);
+
+ SetValueInternal(key, obj);
+ }
+
+ // ===== GetArray =====
+
+ ///
+ /// Gets the PdfArray with the specified key, or null, if no such object exists and should not be created.
+ /// If the key refers to a reference, the referenced PdfArray is returned.
+ /// If the array exists, but cannot be transformed to type T, an exception is thrown.
+ ///
+ /// The key.
+ /// The creation options.
+ ///
+ public PdfArray? GetArray(string key, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ Name.EnsureName(key);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ return GetValueInternal(key, options, valueType, valueType != null) as PdfArray;
+ }
+
+ public PdfArray GetRequiredArray(string key, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+
+ {
+ return GetArray(key, options, valueType)
+ ?? throw ExceptionOnNull(key);
+ }
+
+ public bool TryGetArray(string key, [MaybeNullWhen(false)] out PdfArray array)
+ => TryGetArray(key, out array, null);
+
+
+ public bool TryGetArray(string key, [MaybeNullWhen(false)] out PdfArray array,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType)
+ {
+ Name.EnsureName(key);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ array = null;
+ if (GetValueInternal(key, VCF.NoTransform, valueType, false) is PdfArray value)
+ {
+ array = value;
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Gets the PdfArray with the specified key, or null, if no such object exists and should not be created.
+ /// If the key refers to a reference, the referenced PdfArray is returned.
+ /// If the array exists, but cannot be transformed to type T, an exception is thrown.
+ ///
+ /// The key.
+ /// The creation options.
+ public T? GetArray<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, VCF options = VCF.None) where T : PdfArray
+ {
+ Name.EnsureName(key);
+
+ var array = GetValueInternal(key, options, typeof(T), true) as T;
+ return array;
+ }
+
+ ///
+ /// Gets the PdfArray with the specified key, or throws an exception, if no such object exists and
+ /// should not be created.
+ /// If the key refers to a reference, the referenced PdfArray is returned.
+ /// If the array exists, but cannot be transformed to type T, an exception is thrown.
+ ///
+ /// The key.
+ /// The creation options.
+ public T GetRequiredArray<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, VCF options = VCF.None) where T : PdfArray
+ {
+ Name.EnsureName(key);
+
+ return GetArray(key, options)
+ ?? throw ExceptionOnNull(key);
+ }
+
+ public bool TryGetArray<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, [MaybeNullWhen(false)] out T array) where T : PdfArray
+ {
+ Name.EnsureName(key);
+
+ array = null;
+ if (GetValueInternal(key, VCF.NoTransform, typeof(T), false) is T valueOfT)
+ {
+ array = valueOfT;
+ return true;
+ }
+ return false;
+ }
+
+ // ===== GetDictionary =====
+
+ ///
+ /// Gets the PdfDictionary with the specified key, or null, if no such object exists
+ /// and should not be created.
+ /// If the key refers to a reference, the referenced PdfDictionary is returned.
+ ///
+ public PdfDictionary? GetDictionary(string key, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ Name.EnsureName(key);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ return GetValueInternal(key, options, valueType, valueType != null) as PdfDictionary;
+ }
+
+ public PdfDictionary GetRequiredDictionary(string key, VCF options = VCF.None,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType = null)
+ {
+ return GetDictionary(key, options, valueType)
+ ?? throw ExceptionOnNull(key);
+ }
+
+ public bool TryGetDictionary(string key, [MaybeNullWhen(false)] out PdfDictionary dict)
+ => TryGetDictionary(key, out dict, null);
+
+ public bool TryGetDictionary(string key, [MaybeNullWhen(false)] out PdfDictionary dict,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType)
+ {
+ Name.EnsureName(key);
+ if (valueType != null)
+ EnsureValueType(valueType);
+
+ dict = null;
+ if (GetValueInternal(key, VCF.NoTransform, valueType, false) is PdfDictionary value)
+ {
+ dict = value;
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Gets the PdfDictionary with the specified key, or null, if no such object exists and
+ /// should not be created.
+ /// If the key refers to a reference, the referenced PdfDictionary is returned.
+ /// If the dictionary exists, but cannot be transformed to type T, an exception is thrown.
+ ///
+ /// The type of the PDF dictionary.
+ /// The key.
+ /// The creation options.
+ public T? GetDictionary<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, VCF options = VCF.None) where T : PdfDictionary
+ {
+ Name.EnsureName(key);
+
+ var value = GetValueInternal(key, options, typeof(T), true);
+ return value as T;
+ }
+
+ ///
+ /// Gets the PdfDictionary with the specified key, or throws an exception, if no such object exists and
+ /// should not be created.
+ /// If the key refers to a reference, the referenced PdfDictionary is returned.
+ /// If the dictionary exists, but cannot be transformed to type T, an exception is thrown.
+ ///
+ /// The type of the PDF dictionary.
+ /// The key.
+ /// The creation options.
+ public T GetRequiredDictionary<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, VCF options = VCF.None) where T : PdfDictionary
+ {
+ return GetDictionary(key, options)
+ ?? throw ExceptionOnNull(key);
+ }
+
+ public bool TryGetDictionary<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
+ (string key, [MaybeNullWhen(false)] out T dict) where T : PdfDictionary
+ {
+ Name.EnsureName(key);
+
+ dict = null;
+ if (GetValueInternal(key, VCF.NoTransform, typeof(T), false) is T valueOfT)
+ {
+ dict = valueOfT;
+ return true;
+ }
+ return false;
+ }
+
+ // ===== PdfReference =====
+
+ ///
+ /// Gets the PdfReference with the specified key, or null if no such object exists.
+ ///
+ public PdfReference? GetReference(string key)
+ {
+ Name.EnsureName(key);
+
+ // Always get the raw value.
+ if (_elements.TryGetValue(key, out var value))
+ return value as PdfReference;
+ return null;
+ }
+
+ ///
+ /// Gets the PdfReference with the specified key, or throws an exception,
+ /// if no such object exists.
+ ///
+ public PdfReference GetRequiredReference(string key)
+ {
+ Name.EnsureName(key);
+
+ return GetReference(key)
+ ?? throw new InvalidOperationException(SyMsgs.IndirectReferenceMustNotBeNull.Message);
+ }
+
+ ///
+ /// Sets the entry to an indirect reference.
+ /// The specified object must be an indirect object,
+ /// otherwise an exception is raised.
+ ///
+ [Obsolete("Use SetObject with an indirect object.")]
+ public void SetReference(string key, PdfObject obj)
+ {
+ Name.EnsureName(key);
+
+ if (obj.Reference == null)
+ throw new ArgumentException("PdfObject is not an indirect object.", nameof(obj));// #SyMsg
+ SetValueInternal(key, obj.Reference);
+ }
+
+ ///
+ /// Sets the entry to an indirect reference.
+ ///
+ public void SetReference(string key, PdfReference iref)
+ {
+ Name.EnsureName(key);
+
+ if (iref is null)
+ throw new ArgumentNullException(nameof(iref));
+ SetValueInternal(key, iref);
+ }
- internal int GetEnumFromName(string key, object defaultValue, bool create)
+ ///
+ /// Access a key that may contain an array or a single item for working with its value(s).
+ ///
+ public ArrayOrSingleItemHelper ArrayOrSingleItem => new(this); // TODO_OLD PDFsharp6: Naming.
+
+ // ===== Enum =====
+
+ public int GetEnumFromName(string key, object defaultValue, bool create = false) // TODO: review, object => Enum?
{
- if (defaultValue is not Enum)
- throw new ArgumentException(nameof(defaultValue));
+ Name.EnsureName(key);
- var obj = this[key];
- if (obj == null)
+ var item = this[key];
+ if (item == null)
{
if (create)
- this[key] = new PdfName(defaultValue.ToString()!);
+ SetValueInternal(key, new PdfName(defaultValue.ToString()!));
// ReSharper disable once PossibleInvalidCastException because Enum objects can always be cast to int.
return (int)defaultValue;
}
- return (int)Enum.Parse(defaultValue.GetType(), obj.ToString()?.Substring(1) ?? "", false);
- }
- internal int GetEnumFromName(string key, object defaultValue)
- => GetEnumFromName(key, defaultValue, false);
+ PdfReference.Dereference(ref item); // Could be a reference. #US373
+ return (int)Enum.Parse(defaultValue.GetType(), item.ToString()?[1..] ?? "", false);
+ }
- internal void SetEnumAsName(string key, object value)
+ public void SetEnumAsName(string key, object value)
{
+ Name.EnsureName(key);
+
if (value is not Enum)
- throw new ArgumentException(nameof(value));
- _elements[key] = new PdfName("/" + value);
+ throw new ArgumentException("value is not an Enum.", nameof(value));
+ SetValueInternal(key, new PdfName("/" + value));
}
- ///
- /// Gets the value for the specified key. If the value does not exist, it is optionally created.
+ public bool TryGetEnum(string key, out TEnum result) where TEnum : struct, Enum
+ {
+ result = default;
+
+ if (TryGetInteger(key, out var value))
+ {
+ result = (TEnum)(value as object);
+ return true;
+ }
+
+ if (TryGetName(key, out var name))
+ {
+ // Name must always have a leading solidus.
+ Debug.Assert(!String.IsNullOrEmpty(name));
+
+ result = Enum.Parse(name?[1..] ?? "", false);
+
+ return true;
+ }
+ return false;
+ }
+
+ public TEnum? GetEnum(string key) where TEnum : struct, Enum
+ {
+ if (TryGetEnum(key, out var result))
+ {
+ return result;
+ }
+ return null;
+ }
+
+
+ // ===== Internal =====
+
+ /// // TODO more docs
+ /// Gets the value for the specified key.
+ /// If the value does not exist, it is optionally created.
+ /// If the value exists, it can be optionally transformed to a derived type.
+ /// If the value is a PdfReference, the indirect object is returned.
///
- public PdfItem? GetValue(string key, VCF options)
+ PdfItem? GetValueInternal(string key, VCF options,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? valueType, bool throwOnTypeMismatch)
{
- var value = this[key];
- if (value is null or PdfNull or PdfReference { Value: PdfNullObject })
+#if DEBUG_
+ // Are we reading a PDF document?
+ if ((OwningDictionary.Document?.IrefTable ?? null) is { IsUnderConstruction: true })
+ {
+ //Debugger.Break();
+ _ = typeof(int);
+ }
+#endif
+#if DEBUG_
+ if (typeof(PdfNameDictionary) == valueType)
+ _ = typeof(int);
+#endif
+ // Name.EnsureName(key) must be done by caller.
+
+ bool valueIsAlreadyOfSuitableType = false;
+ if (!_elements.TryGetValue(key, out var value))
+ {
+ // Case: Dictionary entry with specified key does not exist.
+
+ // Create the value if requested.
+ if (options is VCF.Create or VCF.CreateIndirect)
+ {
+ var type = valueType ?? GetValueType(key);
+ if (type != null)
+ {
+ // Case: value is null, but we have a type to create it.
+
+ //Debug.Assert(typeof(PdfObject).IsAssignableFrom(type), "Type not allowed.");
+ //PdfObject? obj;
+
+ if (typeof(PdfContainer).IsAssignableFrom(type))
+ {
+ // Case: Create PDF array or dictionary.
+ var obj = CreateContainer(type, null, options is VCF.CreateIndirect);
+
+ if (options is VCF.CreateIndirect)
+ {
+ // There are dictionaries (e.g. PdfNameDictionary) that are always created as indirect
+ // in their constructor.
+ if (obj.IsIndirect is false) // TODO Check this everywhere
+ OwningDictionary.Owner.IrefTable.Add(obj);
+ Debug.Assert(obj.Reference is not null);
+ }
+ else // if (options is VCF.Create) is always true here.
+ {
+ Debug.Assert(obj.Reference is null);
+ }
+ value = obj;
+ }
+ // Note that the cases below are not real world scenarios, but you cannot force a developer not to write code like this:
+ // var item = new PdfDictionary().Elements.GetValue("/SomeKey", VCF.Create)
+ // This creates an empty PdfString which is senseless, but valid.
+ else if (typeof(PdfPrimitive).IsAssignableFrom(type))
+ {
+ // Case: Create a PDF primitive. This makes no sense because it is immutable, and we have no default value.
+
+ // Primitives cannot be indirect objects.
+ if (options is VCF.CreateIndirect)
+ throw new InvalidOperationException($"Cannot create an indirect object of type '{type.FullName}'.");
+
+ value = (PdfItem)(Activator.CreateInstance(type)
+ ?? throw new InvalidOperationException($"Cannot create instance of type '{type.FullName}'."));
+
+ value.SetTransformed();
+ }
+ else if (typeof(PdfPrimitiveObject).IsAssignableFrom(type))
+ {
+ // Case: Create a PDF primitive object. This makes even less sense and is not implemented.
+
+ // Primitive objects cannot be direct.
+ if (options is VCF.Create)
+ throw new InvalidOperationException($"Cannot create a direct object of type '{type.FullName}'.");
+
+ throw new InvalidOperationException(
+ $"Cannot create instance of type '{type.FullName}' " +
+ "because creating of indirect primitive PDF objects makes less sense and is not implemented.");
+ }
+ else if (typeof(PdfReference) == type)
+ {
+ // Case: itemType is PdfReference in combination with Create/CreateIndirect.
+
+ throw new InvalidOperationException($"Cannot combine type '{type.FullName}' with creation flags.");
+ }
+ else
+ {
+ // Case: We only come here for special types like PdfDocument which are derived from PdfObject but are not containers.
+ // Just throw.
+
+ throw new InvalidOperationException($"Cannot create instance of type '{type.FullName}' " +
+ "because this is not intended for this PDF type.");
+ }
+
+ Debug.Assert(value.IsTransformed);
+
+ SetValueInternal(key, value);
+
+ Debug.Assert(valueType == null || valueType.IsInstanceOfType(value));
+ valueIsAlreadyOfSuitableType = true;
+ //return value;
+ }
+ else
+ {
+ // Case: We have no type and cannot create an instance.
+ // But check if this is caused by to aggressive trimming the assembly.
+
+ // /Info is the first type created by object type transformation.
+ // In case somebody trimmed PDFsharp to hard, we come here und try to give a
+ // meaningful explanation.
+ if (key == "/Info")
+ {
+ // We come here if PDFsharp was fully trimmed and meta-data could not be found by reflection.
+ throw new InvalidOperationException(
+ "PDFsharp relies on reflection and does not work when a fully-trimmed self-contained file is used.\r\n" +
+ "See https://docs.pdfsharp.net/ for further information.");
+ }
+
+ // Maybe missing or wrong meta configuration.
+ throw new InvalidOperationException(
+ $"Cannot create value for key '{key}' because no type was found.");
+ }
+ }
+ }
+ else
{
- if (options != VCF.None)
+ // Case: The value exists and can be returned.
+ // But /*for imported documents*/ check for object type transformation.
+
+ if (value is PdfReference iref)
{
#if DEBUG_
- if (key == "/Resources")
- Debug-Break.Break();
+ var irefTable = OwningDictionary.Document?.IrefTable ?? null;
+ if (irefTable is { IsUnderConstruction: true })
+ {
+ //Debug.Assert(false, "Should not happen anymore.");
+ _ = typeof(int);
+ }
#endif
- var type = GetValueType(key);
- if (type != null)
+ // Case: value is an indirect object. It can only be a container.
+
+#if true_ // TODO Create US for this case and DELETE the code.
+ // Check a very particular case first that can happen during
+ // the timespan when the IrefTable is not completely read.
+ var irefTable = OwningDictionary.Document?.IrefTable ?? null;
+ if (irefTable is { IsUnderConstruction: true })
{
- Debug.Assert(typeof(PdfItem).IsAssignableFrom(type), "Type not allowed.");
- PdfObject? obj;
- if (typeof(PdfDictionary).IsAssignableFrom(type))
+ // Case: During the import of a PDF document GetValue is called.
+ // This happens only during encryption.
+ // IMPROVE: Prevent from coming here. Check if we need temp irefs anymore.
+ var newIref = irefTable[iref.Value.ObjectID];
+ if (newIref == null)
{
- value = obj = CreateDictionary(type, null);
+ // Case: Cannot happen. We can have irefs with no value, but not
+ // indirect objects with no reference.
+ _ = typeof(int);
}
- else if (typeof(PdfArray).IsAssignableFrom(type))
+ else if (ReferenceEquals(iref, newIref) is false)
{
- value = obj = CreateArray(type, null);
+ // Case: We are reading a PDF document that hast more than one XRef table or xref streams.
+ // A top level object can contain an indirect reference to an object with an ID that does not yet exist.
+ // TODO more checks...
+ if (ReferenceEquals(iref.Value, newIref.Value))
+ {
+ iref = newIref;
+ iref.Value.Reference = iref;
+ UpdateValueInternal(key, iref);
+ }
}
else
- throw new NotImplementedException("Type other than array or dictionary.");
-
- if (options == VCF.CreateIndirect)
{
- _ownerDictionary?.Owner.IrefTable.Add(obj);
- this[key] = obj.Reference;
+ _ = typeof(int);
}
- else
- this[key] = obj;
}
- else
+#if DEBUG
+ else if (irefTable != null)
{
- if (key == "/Info")
+ // TO/DO: Check if already required.
+ var altIref = irefTable[iref.Value.ObjectID];
+ if (altIref == null)
{
- // We come here if PDFsharp was fully trimmed and meta-data could not be found by reflection.
- // Note: Should not occur since we added attributes that prevent trimming of certain parts.
- throw new InvalidOperationException($"PDFsharp relies on reflection and does not work when a fully-trimmed self-contained file is used.\r\n" +
- $"See {UrlLiterals.LinkToRoot} for further information.");
+ // Should not come here.
+ _ = typeof(int);
}
- else
+ else if (ReferenceEquals(iref, altIref) is false)
{
- throw new InvalidOperationException("Cannot create value for key: " + key);
+ // Should not come here.
+ iref = altIref;
+ UpdateValueInternal(key, iref);
}
}
- }
- }
- else
- {
- // The value exists and can be returned. But for imported documents check for necessary
- // object type transformation.
- PdfReference? iref;
- if ((iref = value as PdfReference) != null)
- {
- // Case: value is an indirect reference.
+#endif
+#endif
+ // Is PdfReference explicitly requested?
+ if (valueType == typeof(PdfReference))
+ return value;
+
+ // In all other cases use the referenced value.
value = iref.Value;
if (value == null)
{
// If we come here PDF file is corrupted.
+ // TODO: What if we have a reference to a non-existing object?
+ // A reference without a value can only happen during loading an existing PDF file.
throw new InvalidOperationException("Indirect reference without value.");
}
- if (true) // || _owner.Document.IsImported)
+ // The nesting can be simplified, but keep it as it is for better understandability.
+ if (options != VCF.NoTransform)
{
- var type = GetValueType(key);
- Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support.");
+ // Get type from parameter or metadata.
+ var type = valueType ?? GetValueType(key);
+
+ // Try transformation only once. If it fails, don’t try again.
+ // Should we try transformation?
- if (type != null! && type != value.GetType())
+ // TODO: What if valueType more derived than metadata type? Transform again?
+ if (value.ShouldTryTransformation
+ || type != null
+ || value.GetType().IsAssignableFrom(type))
{
- if (typeof(PdfDictionary).IsAssignableFrom(type))
+ // Do we have a type anyway?
+ if (type != null)
{
- Debug.Assert(value is PdfDictionary, "Bug in PDFsharp. Please send this file to PDFsharp support.");
- value = CreateDictionary(type, (PdfDictionary)value);
+ // Case: We have a type and an existing indirect object.
+
+ if (type.IsInstanceOfType(value))
+ {
+ // Case: The value is already of the appropriate type.
+
+ // Set to transformed, but only if the requested type is not one of the base types.
+ if (type != typeof(PdfDictionary) && type != typeof(PdfArray))
+ value.SetTransformed();
+ }
+ else if (value is PdfContainer cont)
+ {
+ // Case: Transform array or dictionary.
+
+ // TODO: Test to transform twice.
+
+ //Debug.Assert(cont.IsTransformed is false or true);
+ Debug.Assert(cont.ParentInfo is null);
+ //if (OwningDictionary.Document.IrefTable.IsUnderConstruction)
+ value = CreateContainer(type, cont, cont.IsIndirect);
+ Debug.Assert(cont.IsDead);
+ Debug.Assert(value.IsTransformed);
+ Debug.Assert(cont.ParentInfo is null);
+
+ }
+ else if (value is PdfPrimitiveObject)
+ {
+ throw new InvalidOperationException(
+ $"Primitive indirect object of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'.");
+ }
+ else
+ {
+ // Exotic case: Reference to e.g. PdfDocument.
+ // Just throw.
+ throw new InvalidOperationException(
+ $"Indirect object of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'.");
+ }
+ // TODO: Should fail because it is a Reference - test this.
+ SetValueInternal(key, value);
}
- else if (typeof(PdfArray).IsAssignableFrom(type))
+ else
{
- Debug.Assert(value is PdfArray, "Bug in PDFsharp. Please send this file to PDFsharp support.");
- value = CreateArray(type, (PdfArray)value);
+ // TODO: Should be possible to transform later.
+ value.SetTransformationTried();
}
- else
- throw new NotImplementedException("Type other than array or dictionary.");
}
}
- return value;
+ Debug.Assert(value != null);
+ //return value;
}
-
- // Transformation is only possible after PDF import.
- if (true) // || _owner.Document.IsImported)
+ else
{
- // Case: value is a direct object
- //if ((dict = value as PdfDictionary) != null)
- if (value is PdfDictionary dict)
+ // Case: value is a direct object.
+
+ // Transformation is possible after PDF import or the user
+ // creates a less derived container (e.g. in unit tests).
+ if (options != VCF.NoTransform)
{
- Debug.Assert(!dict.IsIndirect);
+ // Try transformation only once. If it fails, don’t try again.
+ // Should we try transformation?
+ if (value.ShouldTryTransformation)
+ {
+ // Get type from parameter or metadata.
+ var type = valueType ?? GetValueType(key);
- var type = GetValueType(key);
- Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support.");
- if (dict.GetType() != type)
- dict = CreateDictionary(type, dict);
- return dict;
+ // Do we have a type anyway?
+ if (type != null)
+ {
+ // Case: We have a type and an existing primitive or direct object.
+
+ // Handle special case PdfRectangle first.
+ if (type == typeof(PdfRectangle)) // TODO in Array and in non-else case above.
+ {
+ if (value is PdfArray { Elements.Count: 4 } array)
+ {
+ value = new PdfRectangle(array);
+ }
+ else
+ {
+ if (throwOnTypeMismatch)
+ throw new InvalidOperationException($"Cannot create PdfRectangle from type '{value.GetType().FullName}'.");
+ value = null;
+ }
+ }
+ else if (type.IsInstanceOfType(value))
+ {
+ // Case: The value is already of the appropriate type.
+
+ // Set to transformed, but only if the requested type is not one of the base types.
+ if (type != typeof(PdfDictionary) && type != typeof(PdfArray))
+ value.SetTransformed();
+ }
+ else if (value is PdfContainer cont)
+ {
+ // Case: Transform direct array or dictionary.
+
+ Debug.Assert(cont.IsIndirect is false);
+ Debug.Assert(cont.IsTransformed is false);
+ Debug.Assert(cont.ParentInfo is not null);
+
+ value = CreateContainer(type, cont, false /*cont.IsIndirect*/);
+ SetValueInternal(key, value);
+
+ Debug.Assert(cont.IsDead);
+ Debug.Assert(value.IsTransformed); // TODO: Can transform twice?
+ Debug.Assert(cont.ParentInfo is null);
+ Debug.Assert(((PdfContainer)value).ParentInfo is not null);
+ }
+ else if (value is PdfNull nullObject)
+ {
+ // TODO Add in Array
+ _ = typeof(int);
+ }
+ // #US373 begin
+ else if (value is PdfInteger integer &&
+ type == typeof(PdfReal))
+ {
+ value = new PdfReal(integer.Value);
+ SetValueInternal(key, value);
+ }
+ // #US373 end
+ else
+ {
+ // Exotic case: value is not a PDF array or dictionary, but not from the requested type.
+ // Just throw.
+ throw new InvalidOperationException(
+ $"Value of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'.");
+ }
+ }
+ else
+ {
+ // TODO: Should be possible to transform later.
+ value.SetTransformationTried();
+ }
+ }
}
+ }
+ }
- //if ((array = value as PdfArray) != null)
- if (value is PdfArray array)
- {
- Debug.Assert(!array.IsIndirect);
-
- var type = GetValueType(key);
- // This is more complicated. If type is null do nothing
- //Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support.");
- if (type != null && type != array.GetType())
- array = CreateArray(type, array);
- return array;
- }
+ // Ensure not a type mismatch.
+ if (!valueIsAlreadyOfSuitableType && value != null && valueType != null)
+ {
+ //if (!valueType.IsAssignableFrom(value.GetType()))
+ if (!valueType.IsInstanceOfType(value))
+ {
+ if (throwOnTypeMismatch)
+ throw ExceptionOnTypeMismatch(key, valueType, value.GetType());
+ return null;
}
}
return value;
}
///
- /// Short cut for GetValue(key, VCF.None).
- ///
- public PdfItem? GetValue(string key)
- => GetValue(key, VCF.None);
-
- ///
- /// Returns the type of the object to be created as value of the specified key.
+ /// Implementation of SetValue.
+ /// Handles setting same value, setting indirect object,
+ /// and releasing old value.
+ /// Keep in sync with PdfArray.
///
- [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- Type? GetValueType(string key) // TODO_OLD: move to PdfObject
+ void SetValueInternal(string key, PdfItem value, bool mustNotExist = false)
{
- Type? type = null;
- var meta = _ownerDictionary.Meta;
- if (meta != null!)
+#if DEBUG_
+ // Are we reading a PDF document?
+ if ((OwningDictionary.Document?.IrefTable ?? null) is { IsUnderConstruction: true })
{
- var kd = meta[key];
- if (kd != null)
- type = kd.GetValueType();
- //else
- // Deb/ug.WriteLine("Warning: Key not descriptor table: " + key); // TODO_OLD: check what this means...
+ //Debugger.Break();
+ _ = typeof(int);
}
- //else
- // Deb/ug.WriteLine("Warning: No meta provided for type: " + _owner.GetType().Name); // TODO_OLD: check what this means...
- return type;
- }
+#endif
+ // Already asserted from caller.
+ Debug.Assert(value is not null);
- PdfArray CreateArray(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- Type type,
- PdfArray? oldArray)
- {
-#if true
- PdfArray? array;
- if (oldArray == null)
- {
- // Use constructor with signature 'Ctor(PdfDocument owner)'.
- var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- null, [typeof(PdfDocument)], null);
- Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name);
- //array = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfArray;
- array = ctorInfo.Invoke([_ownerDictionary.Owner]) as PdfArray;
- }
- else
- {
- // Use constructor with signature 'Ctor(PdfDictionary dict)'.
- var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- null, types: [typeof(PdfArray)], null);
- Debug.Assert(ctorInfo != null, $"No appropriate constructor found for type: {type.Name}.");
- //array = ctorInfo.Invoke(new object[] { oldArray }) as PdfArray;
- array = ctorInfo.Invoke([oldArray]) as PdfArray;
- }
- return array ?? NRT.ThrowOnNull();
-#else
- // Rewritten Win_RT style.
- PdfArray array = null;
- if (oldArray == null)
- {
- // Use constructor with signature 'Ctor(PdfDocument owner)'.
- var ctorInfos = type.GetTypeInfo().DeclaredConstructors; //.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- //null, new Type[] { typeof(PdfDocument) }, null);
- foreach (var ctorInfo in ctorInfos)
- {
- var parameters = ctorInfo.GetParameters();
- if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument))
- {
- array = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfArray;
- break;
- }
- }
- Debug.Assert(array != null, "No appropriate constructor found for type: " + type.Name);
- }
+ if (value.IsDead)
+ throw new InvalidOperationException("TODO: Is Dead."); // /messages/ObjectIsDead.html
+
+ // Special treatment for PdfRectangle.
+ // Convert to direct PdfArray.
+ if (value is PdfRectangle rect)
+ value = rect.GetAsArrayOfValues();
else
+ PdfReference.ToReference(ref value);
+
+ // Is value already set?
+ if (_elements.TryGetValue(key, out var oldItem))
{
- // Use constructor with signature 'Ctor(PdfDictionary dict)'.
- var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // .GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- //null, new Type[] { typeof(PdfArray) }, null);
- foreach (var ctorInfo in ctorInfos)
- {
- var parameters = ctorInfo.GetParameters();
- if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfArray))
- {
- array = ctorInfo.Invoke(new object[] { oldArray }) as PdfArray;
- break;
- }
- }
- Debug.Assert(array != null, "No appropriate constructor found for type: " + type.Name);
+ if (mustNotExist)
+ throw new InvalidOperationException($"Key '{key}' already exists.");
}
- return array;
-#endif
- }
- PdfDictionary CreateDictionary(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- Type type,
- PdfDictionary? oldDictionary)
- {
-#if true
- ConstructorInfo? ctorInfo;
- PdfDictionary? dict;
- if (oldDictionary == null)
+ // Check self-assignment because of SetDead.
+ if (ReferenceEquals(oldItem, value))
{
- // Use constructor with signature 'Ctor(PdfDocument owner)'.
- ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- null, [typeof(PdfDocument)], null);
- Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name);
- dict = ctorInfo.Invoke([_ownerDictionary.Owner]) as PdfDictionary;
+ LogWarning();
+ return;
}
- else
+
+ if (value is PdfObject obj)
{
- // Use constructor with signature 'Ctor(PdfDictionary dict)'.
- ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- null, [typeof(PdfDictionary)], null);
- Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name);
- dict = ctorInfo.Invoke([oldDictionary]) as PdfDictionary;
- }
- return dict ?? NRT.ThrowOnNull();
-#else
- // Rewritten Win_RT style.
- PdfDictionary dict = null;
- if (oldDictionary == null)
- {
- // Use constructor with signature 'Ctor(PdfDocument owner)'.
- var ctorInfos = type.GetTypeInfo().DeclaredConstructors; //GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- //null, new Type[] { typeof(PdfDocument) }, null);
- foreach (var ctorInfo in ctorInfos)
+ if (obj.Reference != null)
{
- var parameters = ctorInfo.GetParameters();
- if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument))
+ Debug.Assert(false, "Should not come here anymore.");
+
+ // Case: Indirect object
+
+ Debug.Assert(obj.ParentInfo is null, "An indirect object must not have a structure parent.");
+ value = obj.Reference;
+ if (ReferenceEquals(oldItem, value))
{
- dict = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfDictionary;
- break;
+ LogWarning();
+ return;
}
}
- Debug.Assert(dict != null, "No appropriate constructor found for type: " + type.Name);
- }
- else
- {
- var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDictionary) }, null);
- foreach (var ctorInfo in ctorInfos)
+ else if (obj is PdfPrimitiveObject)
{
- var parameters = ctorInfo.GetParameters();
- if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDictionary))
- {
- dict = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfDictionary;
- break;
- }
- }
- Debug.Assert(dict != null, "No appropriate constructor found for type: " + type.Name);
- }
- return dict;
-#endif
- }
+ // Case: Direct non-container object
- PdfItem CreateValue(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- Type type,
- PdfDictionary? oldValue)
- {
-#if true
- var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- null, [typeof(PdfDocument)], null);
- var obj = ctorInfo!.Invoke([_ownerDictionary.Owner]) as PdfObject;
- if (oldValue != null)
- {
- obj!.Reference = oldValue.Reference;
- if (obj.Reference != null)
- {
- obj.Reference.Value = obj;
- if (obj is PdfDictionary dict)
- dict._elements = oldValue._elements;
+ FailForDirectPrimitiveObject(obj);
}
- }
- return obj ?? NRT.ThrowOnNull();
-#else
- // Rewritten Win_RT style.
- PdfObject obj = null;
- var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDocument) }, null);
- foreach (var ctorInfo in ctorInfos)
- {
- var parameters = ctorInfo.GetParameters();
- if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument))
+ else
{
- obj = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfObject;
- break;
+ // Case: Direct container object
+ Debug.Assert(obj is PdfContainer);
+
+ if (obj.ParentInfo != null)
+ throw new InvalidOperationException("A direct object can only be added once to a container."); // TODO Error code
+
+ obj.SetStructureParent(this, key);
}
}
- Debug.Assert(obj != null, "No appropriate constructor found for type: " + type.Name);
- if (oldValue != null)
+ else
{
- obj.Reference = oldValue.Reference;
- obj.Reference.Value = obj;
- if (obj is PdfDictionary)
- {
- PdfDictionary dict = (PdfDictionary)obj;
- dict._elements = oldValue._elements;
- }
+ // Case: PdfPrimitive or PdfReference
+ Debug.Assert(value is PdfReference or PdfPrimitive);
}
- return obj;
-#endif
- }
-
- ///
- /// Sets the entry with the specified value. DON’T USE THIS FUNCTION - IT MAY BE REMOVED.
- ///
- public void SetValue(string key, PdfItem value)
- {
- //Debug.Assert((value is PdfObject && ((PdfObject)value).Reference == null) | !(value is PdfObject),
- Debug.Assert(value is PdfObject { Reference: null } or not PdfObject,
- "You try to set an indirect object directly into a dictionary.");
- // Hammer the value in without further checks.
_elements[key] = value;
- }
-
- ///
- /// Gets the PdfObject with the specified key, or null if no such object exists. If the key refers to
- /// a reference, the referenced PdfObject is returned.
- ///
- public PdfObject? GetObject(string key)
- {
- var item = this[key];
- if (item is PdfReference reference)
- return reference.Value;
- return item as PdfObject;
- }
-
- ///
- /// Gets the PdfDictionary with the specified key, or null if no such object exists. If the key refers to
- /// a reference, the referenced PdfDictionary is returned.
- ///
- public PdfDictionary? GetDictionary(string key)
- => GetObject(key) as PdfDictionary;
- ///
- /// Gets the PdfArray with the specified key, or null if no such object exists. If the key refers to
- /// a reference, the referenced PdfArray is returned.
- ///
- public PdfArray? GetArray(string key)
- => GetObject(key) as PdfArray;
+ if (oldItem != null)
+ ReleaseItem(oldItem);
- ///
- /// Gets the PdfReference with the specified key, or null if no such object exists.
- ///
- public PdfReference? GetReference(string key)
- => this[key] as PdfReference;
+ return;
- ///
- /// Sets the entry to the specified object. The object must not be an indirect object,
- /// otherwise an exception is raised.
- ///
- public void SetObject(string key, PdfObject obj)
- {
- //if (obj.Reference is not null)
- if (obj.IsIndirect)
- throw new ArgumentException("PdfObject must not be an indirect object.", nameof(obj));
- this[key] = obj;
+ void LogWarning()
+ {
+ //PdfSharpLogHost.Logger.LogWarning("Setting same value in dictionary.");
+ }
}
///
- /// Sets the entry as a reference to the specified object. The object must be an indirect object,
- /// otherwise an exception is raised.
+ /// Returns the type of the object to be created as value of the specified key.
///
- public void SetReference(string key, PdfObject obj)
+ [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors |
+ DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type? GetValueType(string key)
{
- //if (obj.Reference is null)
- if (obj.IsIndirect is false)
- throw new ArgumentException("PdfObject must be an indirect object.", nameof(obj));
- this[key] = obj.Reference;
+ Type? type = null;
+ var meta = OwningDictionary.Meta;
+ if (meta != null!)
+ {
+ var kd = meta[key];
+ if (kd != null)
+ type = kd.GetValueType();
+ }
+ return type;
}
///
- /// Sets the entry as a reference to the specified iref.
+ /// The dictionary this elements object belongs to.
///
- public void SetReference(string key, PdfReference iref)
- {
- if (iref is null)
- throw new ArgumentNullException(nameof(iref));
- this[key] = iref;
- }
+ public PdfDictionary OwningDictionary => (PdfDictionary)OwningContainer;
#region IDictionary Members
@@ -1150,25 +2247,44 @@ public void SetReference(string key, PdfReference iref)
///
/// Returns an object for the object.
///
- public IEnumerator> GetEnumerator()
- {
- return _elements.GetEnumerator();
- }
+ //IEnumerator> IEnumerable>.GetEnumerator()
+ // => (IEnumerator>)GetEnumerator();
+
+ public IEnumerator> GetEnumerator()
+ => _elements.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
+ => ((ICollection)_elements).GetEnumerator();
+
+ PdfItem IDictionary.this[string key]
{
- return ((ICollection)_elements).GetEnumerator();
+ [Obsolete("Make sure references are handled correctly.")]
+ get => this[key]!; // TODO?? Key not found exception? BREAKING?
+ set => this[key] = value;
}
///
/// Gets or sets an entry in the dictionary. The specified key must be a valid PDF name
/// starting with a slash '/'. This property provides full access to the elements of the
/// PDF dictionary. Wrong use can lead to errors or corrupt PDF files.
+ /// TODO: review:
+ /// New in 6.3:
+ /// Gets or sets an entry in the dictionary.
+ /// The getter returns null if no value with the specified key exists.
+ /// If the value is a PdfReference this value is returned, i.e. in contrast to GetValue
+ /// it is not dereferenced to its indirect object.
+ /// The setter throws an ArgumentNullException if the value is null.
+ /// If you try to set an indirect object its PdfReference is set instead.
+ /// It is not possible to have a .NET reference to an indirect PdfObject.
///
public PdfItem? this[string key]
{
+ // #US373 [Obsolete("Make sure references are handled correctly.")]
get
{
+ Name.EnsureName(key);
+
+ // Always get the raw (not dereferenced) value.
_elements.TryGetValue(key, out var item);
return item;
}
@@ -1176,65 +2292,64 @@ public PdfItem? this[string key]
{
if (value == null)
throw new ArgumentNullException(nameof(value));
-#if DEBUG_
- if (key == "/MediaBox")
- _ = typeof(int);
-
- //if (value is PdfObject)
- //{
- // PdfObject obj = (PdfObject)value;
- // if (obj.Reference != null)
- // throw new ArgumentException("An object with an indirect reference cannot be a direct value. Try to set an indirect reference.");
- //}
- if (value is PdfDictionary)
- {
- PdfDictionary dict = (PdfDictionary)value;
- if (dict._stream != null)
- throw new ArgumentException("A dictionary with stream cannot be a direct value.");
- }
-#endif
- if (value is PdfObject { IsIndirect: true } obj)
- value = obj.Reference;
- _elements[key] = value;
+
+ SetValueInternal(key, value);
}
}
+ ///
+ /// Gets or sets an entry in the dictionary identified by a Name.
+ ///
+ public PdfItem? this[Name name]
+ {
+ // #US373 [Obsolete("Make sure references are handled correctly.")]
+ get => this[name.Value];
+ set => this[name.Value] = value;
+ }
+
///
/// Gets or sets an entry in the dictionary identified by a PdfName object.
///
public PdfItem? this[PdfName key]
{
+ // #US373 [Obsolete("Make sure references are handled correctly.")]
get => this[key.Value];
- set
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
-
-#if DEBUG
- if (value is PdfDictionary dictionary)
- {
- var dict = dictionary;
- if (dict.Stream != null)
- throw new ArgumentException("A dictionary with stream cannot be a direct value.");
- }
-#endif
-
- if (value is PdfObject { IsIndirect: true } obj)
- value = obj.Reference;
- _elements[key.Value] = value;
- }
+ set => this[key.Value] = value;
}
///
/// Removes the value with the specified key.
///
- public bool Remove(string key) => _elements.Remove(key);
+ public bool Remove(string key)
+ {
+ // ReSharper disable once InvertIf
+ // ReSharper disable once CanSimplifyDictionaryRemovingWithSingleCall because this is not in .NET Standard
+ if (_elements.TryGetValue(key, out var oldItem))
+ {
+ _elements.Remove(key);
+ ReleaseItem(oldItem);
+ return true;
+ }
+ return false;
+ }
///
/// Removes the value with the specified key.
///
- public bool Remove(KeyValuePair item)
- => throw new NotImplementedException();
+ public bool Remove(KeyValuePair item)
+ {
+ // ReSharper disable once InvertIf
+ if (_elements.TryGetValue(item.Key, out var value))
+ {
+ // ReSharper disable once InvertIf
+ if (ReferenceEquals(item.Value, value))
+ {
+ Remove(item.Key);
+ return true;
+ }
+ }
+ return false;
+ }
///
/// Determines whether the dictionary contains the specified name.
@@ -1242,38 +2357,59 @@ public bool Remove(KeyValuePair item)
public bool ContainsKey(string key) => _elements.ContainsKey(key);
///
- /// Determines whether the dictionary contains a specific value.
+ /// Determines whether the dictionary contains a specific key.
///
- public bool Contains(KeyValuePair item)
- => throw new NotImplementedException();
+ public bool Contains(KeyValuePair item)
+ {
+ if (_elements.TryGetValue(item.Key, out var value))
+ {
+ return ReferenceEquals(item.Value, value);
+ }
+ return false;
+ }
///
/// Removes all elements from the dictionary.
///
- public void Clear() => _elements.Clear();
+ public void Clear()
+ {
+ var oldItems = _elements.Values.ToArray();
+ _elements.Clear();
+ foreach (var oldItem in oldItems)
+ ReleaseItem(oldItem);
+ }
///
/// Adds the specified value to the dictionary.
+ /// If the value is an indirect object, the PdfReference is added instead.
+ /// If the value is a direct object that war previously added to another
+ /// dictionary or array, an exception is thrown.
///
- public void Add(string key, PdfItem? value)
- {
- if (String.IsNullOrEmpty(key))
- throw new ArgumentNullException(nameof(key));
+ //void IDictionary.Add(string key, PdfItem? value)
+ // => Add(key, value ?? throw new ArgumentNullException(nameof(value)));
- if (key[0] != '/')
- throw new ArgumentException("The key must start with a slash '/'.");
+ public void Add(string key, PdfItem value)
+ {
+ //if (String.IsNullOrEmpty(key))
+ // throw new ArgumentNullException(nameof(key));
+ //if (key[0] != '/')
+ // throw new ArgumentException("The key must start with a slash '/'.");
+ Name.EnsureName(key);
- // If object is indirect automatically convert value to reference.
- if (value is PdfObject { IsIndirect: true } obj)
- value = obj.Reference;
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
- _elements.Add(key, value);
+ SetValueInternal(key, value);
}
///
/// Adds an item to the dictionary.
///
- public void Add(KeyValuePair item) => Add(item.Key, item.Value);
+ /// If item.Value is null
+ //void ICollection>.Add(KeyValuePair item) =>
+ // Add(item.Key, item.Value ?? throw new ArgumentNullException(nameof(item.Value)));
+
+ public void Add(KeyValuePair item) => Add(item.Key, item.Value);
///
/// Gets all keys currently in use in this dictionary as an array of PdfName objects.
@@ -1284,9 +2420,9 @@ public PdfName[] KeyNames
{
ICollection values = _elements.Keys;
int count = values.Count;
- string[] strings = new string[count];
+ var strings = new string[count];
values.CopyTo(strings, 0);
- PdfName[] names = new PdfName[count];
+ var names = new PdfName[count];
for (int idx = 0; idx < count; idx++)
names[idx] = new PdfName(strings[idx]);
return names;
@@ -1309,16 +2445,12 @@ public ICollection Keys
}
}
- ///
- /// Gets the value associated with the specified key.
- ///
- public bool TryGetValue(string key, out PdfItem? value) => _elements.TryGetValue(key, out value);
-
///
/// Gets all values currently in use in this dictionary as an array of PdfItem objects.
///
- //public ICollection Values
- public ICollection Values
+ //ICollection IDictionary.Values => (ICollection)Values;
+
+ public ICollection Values
{
// It is by design not to return _elements.Values, but a copy.
get
@@ -1352,9 +2484,10 @@ public ICollection Values
///
/// Copies the elements of the dictionary to an array, starting at a particular index.
///
- public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
{
- throw new NotImplementedException();
+ foreach (var element in _elements)
+ array[arrayIndex++] = element;
}
///
@@ -1364,11 +2497,6 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex)
#endregion
- ///
- /// Access a key that may contain an array or a single item for working with its value(s).
- ///
- public ArrayOrSingleItemHelper ArrayOrSingleItem => new(this); // TODO_OLD PDFsharp6: Naming.
-
///
/// Gets the DebuggerDisplayAttribute text.
///
@@ -1379,7 +2507,7 @@ internal string DebuggerDisplay
get
{
var sb = new StringBuilder();
- sb.AppendFormat(CultureInfo.InvariantCulture, "key={0}:(", _elements.Count);
+ sb.AppendFormat(Invariant($"count={_elements.Count} ["));
var addSpace = false;
ICollection keys = _elements.Keys;
foreach (var key in keys)
@@ -1389,31 +2517,47 @@ internal string DebuggerDisplay
addSpace = true;
sb.Append(key);
}
- sb.Append(')');
+ sb.Append(']');
return sb.ToString();
}
}
+ static Exception ExceptionOnNull(string key)
+ {
+ return new InvalidOperationException($"Value at key '{key}' is null.");
+ }
+
+ static Exception ExceptionOnTypeMismatch(string key, Type expected, Type found)
+ {
+ return new InvalidOperationException($"Value at key '{key}' is expected to be of type {expected.FullName}, but is of type {found.FullName}.");
+ }
+
///
/// The elements of the dictionary with a string as key.
/// Because the string is a name it starts always with a '/'.
///
- Dictionary _elements;
-
- ///
- /// The dictionary this object belongs to.
- ///
- PdfDictionary _ownerDictionary;
+ Dictionary _elements = [];
}
///
- /// The PDF stream objects.
+ /// Represents the optional PDF stream of a dictionary.
///
public sealed class PdfStream
{
internal PdfStream(PdfDictionary ownerDictionary)
{
_ownerDictionary = ownerDictionary ?? throw new ArgumentNullException(nameof(ownerDictionary));
+
+ // TODO EnsureIndirect()
+ if (ownerDictionary.IsIndirect is false)
+ {
+ //TODO: Does not work on all tests.
+ // throw new InvalidOperationException("Cannot create a stream for a direct object.");
+
+ // This happened when we create a stream for a PdfDictionary during its creation.
+ // The Dictionary must have an owner, but is not yet added to the iRef table.
+ ownerDictionary.SetMustBeIndirect();
+ }
}
///
@@ -1422,6 +2566,7 @@ internal PdfStream(PdfDictionary ownerDictionary)
internal PdfStream(byte[] value, PdfDictionary owner)
: this(owner)
{
+ // TODO EnsureIndirect()
_value = value;
}
@@ -1445,10 +2590,11 @@ public PdfStream Clone()
///
internal void ChangeOwner(PdfDictionary dict)
{
- if (_ownerDictionary != null!)
- {
- // ???
- }
+ //if (_ownerDictionary != null!)
+ //{
+ // // ???
+ // Debug.Assert(false);
+ //}
// Set new owner.
_ownerDictionary = dict;
@@ -1494,10 +2640,11 @@ public byte[] UnfilteredValue
byte[]? bytes = null;
if (_value != null)
{
- var filter = _ownerDictionary.Elements["/Filter"];
+ var filter = _ownerDictionary.Elements.GetValue(Keys.Filter);
+ //var filter = _ownerDictionary.Elements[Keys.Filter]; #US373
if (filter != null)
{
- var decodeParms = _ownerDictionary.Elements[Keys.DecodeParms];
+ var decodeParms = _ownerDictionary.Elements.GetValue(Keys.DecodeParms);
bytes = Filtering.Decode(_value, filter, decodeParms);
if (bytes == null!)
{
@@ -1511,6 +2658,7 @@ public byte[] UnfilteredValue
_value.CopyTo(bytes, 0);
}
}
+
return bytes ?? [];
}
}
@@ -1524,18 +2672,18 @@ public byte[] UnfilteredValue
[Obsolete("Not correctly implemented. Use the function TryUncompress.")]
public bool TryUnfilter()
{
- // Keep old code for not break existing code.
+ // Keep old code, do not break existing code.
if (_value != null)
{
- var filter = _ownerDictionary.Elements["/Filter"];
+ var filter = _ownerDictionary.Elements.GetValue(Keys.Filter/*, VCF.NoTransform*/);
if (filter != null)
{
// PDFsharp can only uncompress streams that are compressed with the ZIP or LZH algorithm.
- var decodeParms = _ownerDictionary.Elements[Keys.DecodeParms];
+ var decodeParms = _ownerDictionary.Elements.GetValue(Keys.DecodeParms/*, VCF.NoTransform*/);
var bytes = Filtering.Decode(_value, filter, decodeParms);
if (bytes != null!)
{
- _ownerDictionary.Elements.Remove("/Filter");
+ _ownerDictionary.Elements.Remove(Keys.Filter);
_ownerDictionary.Elements.Remove(Keys.DecodeParms);
Value = bytes;
}
@@ -1543,6 +2691,7 @@ public bool TryUnfilter()
return false;
}
}
+
return true;
}
@@ -1556,19 +2705,28 @@ public bool TryUncompress()
{
if (_value != null)
{
- var filter = _ownerDictionary.Elements["/Filter"];
+ var filter = _ownerDictionary.Elements.GetValue(Keys.Filter);
+ // var filter = _ownerDictionary.Elements[Keys.Filter]; #US373
if (filter == null)
return false;
// filter can be an array. We only try to unzip a single filter name.
- var filterName = filter.ToString()!.TrimStart('/');
+ string filterName;
+ if (filter is PdfArray { Elements.Count: 1 } array)
+ {
+ filterName = array.Elements[0].ToString()!.TrimStart('/');
+ }
+ else
+ {
+ filterName = filter.ToString()!.TrimStart('/');
+ }
// PDF 1.7 specs say that the abbreviations are also allowed as filter values.
if (filterName is PdfFilterNames.LzwDecode or PdfFilterNames.LzwDecodeAbbreviation
or PdfFilterNames.FlateDecode or PdfFilterNames.FlateDecodeAbbreviation)
{
// PDFsharp can only uncompress streams that are compressed with the ZIP or LZH algorithm.
- var decodeParms = _ownerDictionary.Elements[Keys.DecodeParms];
+ var decodeParms = _ownerDictionary.Elements.GetValue(Keys.DecodeParms/*, VCF.NoTransform*/);
var bytes = Filtering.Decode(_value, filter, decodeParms);
// Remove the filter and optional decode parameters.
@@ -1591,9 +2749,8 @@ public bool IsFiltered()
{
if (_value != null)
{
- var filter = _ownerDictionary.Elements[Keys.Filter];
- if (filter != null)
- return true;
+ // TODO #US373: Check documentation. Returns false if Filter is set, but stream is not.
+ return _ownerDictionary.Elements.HasValue(Keys.Filter); // #US373
}
return false;
}
@@ -1609,9 +2766,9 @@ public void Zip()
if (!_ownerDictionary.Elements.ContainsKey(Keys.Filter))
{
- _value = Filtering.FlateDecode.Encode(_value, _ownerDictionary._document.Options.FlateEncodeMode);
- _ownerDictionary.Elements["/Filter"] = new PdfName("/FlateDecode");
- _ownerDictionary.Elements["/Length"] = new PdfInteger(_value.Length);
+ _value = Filtering.FlateDecode.Encode(_value, _ownerDictionary.Document.Options.FlateEncodeMode);
+ _ownerDictionary.Elements.SetName(Keys.Filter, "/FlateDecode");
+ _ownerDictionary.Elements.SetInteger(Keys.Length, _value.Length);
}
}
@@ -1623,21 +2780,21 @@ public override string ToString()
if (_value == null)
return "«null»";
- string stream;
- var filter = _ownerDictionary.Elements["/Filter"];
+ string content;
+ var filter = _ownerDictionary.Elements.GetValue(Keys.Filter);
if (filter != null)
{
- var decodeParms = _ownerDictionary.Elements[Keys.DecodeParms];
+ var decodeParms = _ownerDictionary.Elements.GetValue(Keys.DecodeParms);
var bytes = Filtering.Decode(_value, filter, decodeParms);
if (bytes != null!)
- stream = PdfEncoders.RawEncoding.GetString(bytes, 0, bytes.Length);
+ content = PdfEncoders.RawEncoding.GetString(bytes, 0, bytes.Length);
else
throw new NotImplementedException("Unknown filter");
}
else
- stream = PdfEncoders.RawEncoding.GetString(_value, 0, _value.Length);
+ content = PdfEncoders.RawEncoding.GetString(_value, 0, _value.Length);
- return stream;
+ return content;
}
///
@@ -1716,11 +2873,46 @@ public class Keys : KeysBase
}
}
+ ///
+ /// Implements a comparer that is used to sort the keys of a dictionary.
+ /// It puts /Type and /Subtype at the top.
+ ///
+ class KeyComparer : IComparer
+ {
+ public int Compare(PdfName? l, PdfName? r)
+ {
+ if (l != null)
+ {
+ if (r != null)
+ {
+ if (l.Value.Equals("/Type"))
+ return -1;
+ if (r.Value.Equals("/Type"))
+ return 1;
+
+ if (l.Value.Equals("/Subtype"))
+ return -1;
+ if (r.Value.Equals("/Subtype"))
+ return 1;
+
+ return String.Compare(l.Value, r.Value, StringComparison.Ordinal);
+ }
+ return 1;
+ }
+ return r == null ? 0 : -1;
+ }
+ }
+
+ static KeyComparer Comparer => _keyComparer ??= new();
+
+ static KeyComparer? _keyComparer;
+
///
/// Gets the DebuggerDisplayAttribute text.
///
// ReSharper disable UnusedMember.Local
- string DebuggerDisplay => Invariant($"dictionary({ObjectID.DebuggerDisplay},[{Elements.Count}])={_elements?.DebuggerDisplay}");
+ string DebuggerDisplay
+ => Invariant($"{GetType().Name}({ObjectID.DebuggerDisplay}, count={_elements?.Count ?? 0}, cont={_elements?.DebuggerDisplay})");
// ReSharper restore UnusedMember.Local
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionaryExtensions/PdfDictionaryExtensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionaryExtensions/PdfDictionaryExtensions.cs
new file mode 100644
index 00000000..10391623
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionaryExtensions/PdfDictionaryExtensions.cs
@@ -0,0 +1,70 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+//using System.Xml.Linq;
+
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
+
+namespace PdfSharp.Pdf.PdfDictionaryExtensions
+{
+ ///
+ /// Extension methods for PDF dictionaries.
+ ///
+ public static class PdfDictionaryExtensions
+ {
+ public static PdfDictionary Transform(this PdfDictionary dict,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type type)
+ {
+ return TransformInternal(dict, type);
+ }
+
+ public static T Transform<
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ T>
+ (this PdfDictionary dict) where T : PdfDictionary
+ {
+ return (T)TransformInternal(dict, typeof(T));
+
+ }
+
+ static PdfDictionary TransformInternal(PdfDictionary dict,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+ Type type)
+ {
+ if (dict.GetType() == type)
+ return dict;
+
+ var ctorInfo = type.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null, types: [typeof(PdfDictionary)], null);
+ if (ctorInfo == null)
+ throw new InvalidOperationException($"Type '{type.FullName}' has no appropriate constructor for object type transformation.");
+ dict = (PdfDictionary)ctorInfo.Invoke([dict]);
+ return dict;
+ }
+
+ //public static PdfItem? this[this, string name] => null;
+
+ //public static PdfItem? GetRawValue(this PdfDictionary dict, string key)
+ //{
+ // return dict.Elements[key];
+ //}
+
+ //public static PdfItem? GetItem(this PdfDictionary dict, string key)
+ //{
+ // return dict.Elements[key];
+ //}
+
+ //public static void SetItem(this PdfDictionary dict, string key, PdfItem value)
+ //{
+ // if (value == null)
+ // throw new ArgumentNullException(nameof(value));
+
+ // dict.Elements[key] = value;
+ //}
+ }
+}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs
index 68cd0250..777f441f 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs
@@ -1,33 +1,39 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System.Reflection;
-using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
+using PdfSharp.Internal;
+using PdfSharp.Internal.OpenType;
using PdfSharp.Drawing;
using PdfSharp.Events;
-using PdfSharp.Fonts.Internal;
using PdfSharp.Logging;
using PdfSharp.Pdf.Advanced;
+using PdfSharp.Pdf.Attachments;
+using PdfSharp.Pdf.Filters;
+using PdfSharp.Pdf.Forms;
using PdfSharp.Pdf.Internal;
using PdfSharp.Pdf.IO;
-using PdfSharp.Pdf.AcroForms;
-using PdfSharp.Pdf.Filters;
+using PdfSharp.Pdf.Metadata;
+using PdfSharp.Pdf.PdfA;
using PdfSharp.Pdf.Security;
using PdfSharp.Pdf.Signatures;
using PdfSharp.Pdf.Structure;
+using PdfSharp.PlugIn;
using PdfSharp.UniversalAccessibility;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using static PdfSharp.Pdf.PdfDictionary;
-// ReSharper disable InconsistentNaming
-// ReSharper disable ConvertPropertyToExpressionBody
+// Re/Sharper disable InconsistentNaming
+// Re/Sharper disable ConvertPropertyToExpressionBody
namespace PdfSharp.Pdf
{
///
/// Represents a PDF document.
///
- [DebuggerDisplay("(Name={" + nameof(Name) + "})")] // A name makes debugging easier.
- public sealed class PdfDocument : PdfObject, IDisposable
+ [DebuggerDisplay("(Name={" + nameof(Name) + "})")] // A unique name makes debugging easier.
+ public sealed class PdfDocument : IDisposable
{
///
/// Creates a new PDF document in memory.
@@ -36,13 +42,11 @@ public sealed class PdfDocument : PdfObject, IDisposable
public PdfDocument()
{
PdfSharpLogHost.Logger.PdfDocumentCreated(Name);
- //PdfDocument.Gob.AttachDocument(Handle);
- _document = this;
- _creation = DateTime.Now;
- _state = DocumentState.Created;
+ CreationDate = DateTimeOffset.Now;
+ State = DocumentState.Created;
_version = 17;
Initialize();
- Info.CreationDate = _creation;
+ Info.CreationDate = CreationDate;
}
///
@@ -64,23 +68,19 @@ public PdfDocument(string outputFilename) : this()
///
public PdfDocument(Stream outputStream)
{
- _document = this;
- _creation = DateTime.Now;
- _state = DocumentState.Created;
+ CreationDate = DateTimeOffset.Now;
+ State = DocumentState.Created;
_version = 14;
Initialize();
- Info.CreationDate = _creation;
+ Info.CreationDate = CreationDate;
OutStream = outputStream;
}
internal PdfDocument(Lexer lexer)
{
- //PdfDocument.Gob.AttachDocument(Handle);
-
- _document = this;
- _creation = DateTime.Now;
- _state = DocumentState.Imported;
+ CreationDate = DateTimeOffset.Now;
+ State = DocumentState.Imported;
//_info = new PdfInfo(this);
//_pages = new PdfPages(this);
@@ -98,8 +98,8 @@ void Initialize()
//_info = new PdfInfo(this);
_fontTable = new PdfFontTable(this);
_imageTable = new PdfImageTable(this);
- Trailer = new PdfTrailer(this);
IrefTable = new PdfCrossReferenceTable(this);
+ Trailer = new PdfTrailer(this);
Trailer.CreateNewDocumentIDs();
}
@@ -124,8 +124,10 @@ public void Dispose()
void Dispose(bool disposing)
{
- if (_state != DocumentState.Disposed)
+ if (State != DocumentState.Disposed)
{
+ Events.OnDisposed(this, new DocumentEventArgs(this)); // MaOs4StLa Review.
+
if (disposing)
{
// Dispose managed resources.
@@ -136,7 +138,8 @@ void Dispose(bool disposing)
}
//PdfDocument.Gob.DetachDocument(Handle);
}
- _state = DocumentState.Disposed | DocumentState.Saved;
+
+ State = DocumentState.Disposed | DocumentState.Saved;
}
///
@@ -146,31 +149,30 @@ void Dispose(bool disposing)
public object? Tag { get; set; }
///
- /// Temporary hack to set a value that tells PDFsharp to create a PDF/A conform document.
+ /// Sets PDF/A part and conformance level for the document.
+ /// After once set it cannot be changed.
///
- public void SetPdfA() // HACK_OLD
- {
- _isPdfA = true;
- _ = UAManager.ForDocument(this);
- }
+ public void SetPdfA(PdfAFormat format)
+ => GetPdfAManager().SetFormat(format);
///
/// Gets a value indicating that you create a PDF/A conform document.
/// This function is temporary and will change in the future.
///
- public bool IsPdfA => _isPdfA; // HACK_OLD
- bool _isPdfA;
+ public bool IsPdfA => PdfAManager is { IsPdfADocument: true };
///
/// Encapsulates the document’s events.
///
public DocumentEvents Events => _documentEvents ??= new();
+
DocumentEvents? _documentEvents;
///
/// Encapsulates the document’s render events.
///
public RenderEvents RenderEvents => _renderEvents ??= new();
+
RenderEvents? _renderEvents;
///
@@ -185,15 +187,21 @@ public void SetPdfA() // HACK_OLD
static string NewName()
{
#if DEBUG_
- if (PdfDocument.nameCount == 57)
- PdfDocument.nameCount.GetType();
+ if (PdfDocument._nameCount == 57)
+ _ = typeof(int);
#endif
return "Document #" + ++_nameCount;
}
+
static int _nameCount;
- //internal bool CanModify => true;
- internal bool CanModify => _openMode == PdfDocumentOpenMode.Modify;
+ internal bool CanModify => OpenMode == PdfDocumentOpenMode.Modify;
+
+ ///
+ /// Gets or sets a value indicating whether to save a document even if it is imported.
+ /// For development and unit testing only.
+ ///
+ internal bool SaveAnyway { get; set; }
///
/// Closes this instance.
@@ -211,7 +219,7 @@ public void Close()
// Get security handler if document gets encrypted.
var effectiveSecurityHandler = SecuritySettings.EffectiveSecurityHandler;
- var writer = new PdfWriter(OutStream, _document, effectiveSecurityHandler);
+ var writer = new PdfWriter(OutStream, this, effectiveSecurityHandler);
try
{
DoSaveAsync(writer).GetAwaiter().GetResult();
@@ -241,11 +249,11 @@ public async Task SaveAsync(string path)
{
EnsureNotYetSaved();
- if (!CanModify)
+ if (!SaveAnyway && !CanModify)
throw new InvalidOperationException(PsMsgs.CannotModify);
// We need ReadWrite when adding a signature. Write is sufficient if not adding a signature.
- var fileAccess = _digitalSignatureHandler == null ? FileAccess.Write : FileAccess.ReadWrite;
+ var fileAccess = DigitalSignatureHandler == null ? FileAccess.Write : FileAccess.ReadWrite;
// ReSharper disable once UseAwaitUsing because we need no DisposeAsync for a simple FileStream.
using var stream = new FileStream(path, FileMode.Create, fileAccess, FileShare.None);
@@ -273,7 +281,7 @@ public async Task SaveAsync(Stream stream, bool closeStream = false)
if (!stream.CanWrite)
throw new InvalidOperationException(PsMsgs.StreamMustBeWritable);
- if (!CanModify)
+ if (!SaveAnyway && !CanModify)
throw new InvalidOperationException(PsMsgs.CannotModify);
// #PDF-A
@@ -291,8 +299,7 @@ public async Task SaveAsync(Stream stream, bool closeStream = false)
PdfWriter? writer = null;
try
{
- Debug.Assert(ReferenceEquals(_document, this));
- writer = new(stream, _document, effectiveSecurityHandler);
+ writer = new(stream, this, effectiveSecurityHandler);
await DoSaveAsync(writer).ConfigureAwait(false);
}
finally
@@ -309,6 +316,7 @@ public async Task SaveAsync(Stream stream, bool closeStream = false)
stream.Position = 0; // Reset the stream position if the stream is kept open.
}
}
+
writer?.Close(closeStream);
}
}
@@ -325,16 +333,20 @@ async Task DoSaveAsync(PdfWriter writer)
if (OutStream != null)
{
// Give feedback if the wrong constructor was used.
- throw new InvalidOperationException("Cannot save a PDF document with no pages. Do not use \"public PdfDocument(string filename)\" or \"public PdfDocument(Stream outputStream)\" if you want to open an existing PDF document from a file or stream; use PdfReader.Open() for that purpose.");
+ throw new InvalidOperationException(
+ "Cannot save a PDF document with no pages. Do not use \"public PdfDocument(string filename)\" or " +
+ "\"public PdfDocument(Stream outputStream)\" if you want to open an existing PDF document from a file or stream. " +
+ "Use PdfReader.Open() for that purpose.");
}
+
throw new InvalidOperationException("Cannot save a PDF document with no pages.");
}
try
{
// Prepare for signing.
- if (_digitalSignatureHandler != null)
- await _digitalSignatureHandler.AddSignatureComponentsAsync().ConfigureAwait(false);
+ if (DigitalSignatureHandler != null)
+ await DigitalSignatureHandler.AddSignatureComponentsAsync().ConfigureAwait(false);
// Remove XRefTrailer
if (Trailer is PdfCrossReferenceStream crossReferenceStream)
@@ -349,7 +361,7 @@ async Task DoSaveAsync(PdfWriter writer)
IrefTable.Add(effectiveSecurityHandler);
else
Debug.Assert(IrefTable.Contains(effectiveSecurityHandler.ObjectID));
- Trailer.Elements[PdfTrailer.Keys.Encrypt] = _securitySettings!.SecurityHandler.Reference;
+ Trailer.Elements[PdfTrailer.Keys.Encrypt] = _securitySettings!.SecurityHandler.RequiredReference;
}
else
Trailer.Elements.Remove(PdfTrailer.Keys.Encrypt);
@@ -365,13 +377,23 @@ async Task DoSaveAsync(PdfWriter writer)
{
PdfReference iref = irefs[idx];
#if DEBUG_
- if (iref.ObjectNumber == 378)
+ if (iref.ObjectNumber == 18)
_ = typeof(int);
#endif
iref.Position = writer.Position;
- iref.Value.WriteObject(writer);
+
+ var obj = iref.Value;
+
+ // Enter indirect object in SecurityHandler to allow object encryption key generation for this object.
+ effectiveSecurityHandler?.EnterObject(obj.ObjectID);
+
+ obj.WriteObject(writer);
}
- // ReSharper disable once RedundantCast. Redundant only if 64 bit.
+
+ // Leaving only the last indirect object in SecurityHandler is sufficient, as this is the first time no indirect object is entered anymore.
+ effectiveSecurityHandler?.LeaveObject();
+
+ // ReSharper disable once RedundantCast because it is redundant only if SizeType is 64 bit.
var startXRef = (SizeType)writer.Position;
IrefTable.WriteObject(writer);
writer.WriteRaw("trailer\n");
@@ -381,8 +403,8 @@ async Task DoSaveAsync(PdfWriter writer)
// #Signature: What about encryption + signing ??
// Prepare for signing.
- if (_digitalSignatureHandler != null)
- await _digitalSignatureHandler.ComputeSignatureAndRange(writer).ConfigureAwait(false);
+ if (DigitalSignatureHandler != null)
+ await DigitalSignatureHandler.ComputeSignatureAndRange(writer).ConfigureAwait(false);
//if (encrypt)
//{
@@ -392,20 +414,20 @@ async Task DoSaveAsync(PdfWriter writer)
}
finally
{
- //await writer.Stream.FlushAsync().ConfigureAwait(false);
+ //await writer.Stream.FlushAsync().ConfigureAwait(false); // TODO StL: better call the async version?
writer.Stream.Flush();
// Do not close the stream writer here.
- _state |= DocumentState.Saved;
+ State |= DocumentState.Saved;
}
}
- void PrepareForPdfA() // Just a first hack.
+ void PrepareForPdfA() // Just a first hack.
{
var internals = Internals;
- Debug.Assert(_uaManager != null);
+ //Debug.Assert(_uaManager != null);
// UAManager sets MarkInformation.
- if (_uaManager == null)
+ if (UAManager == null)
{
// Marked must be true in MarkInfo.
var markInfo = new PdfMarkInformation();
@@ -427,8 +449,9 @@ void PrepareForPdfA() // Just a first hack.
outputIntents.Elements.Add("/RegistryName", new PdfString("http://www.color.org"));
outputIntents.Elements.Add("/Info", new PdfString("Creator: ColorOrg Manufacturer:IEC Model:sRGB"));
- var profileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("PdfSharp.Resources.sRGB2014.icc")
- ?? throw new InvalidOperationException("Embedded color profile was not found.");
+ var profileStream = Assembly.GetExecutingAssembly()
+ .GetManifestResourceStream("PdfSharp.Resources.sRGB2014.icc")
+ ?? throw new InvalidOperationException("Embedded color profile was not found.");
var profile = new byte[profileStream.Length];
var read = profileStream.Read(profile, 0, (int)profileStream.Length);
@@ -441,11 +464,11 @@ void PrepareForPdfA() // Just a first hack.
var profileObject = new PdfDictionary(this);
IrefTable.Add(profileObject);
profileObject.Stream = new PdfDictionary.PdfStream(profileCompressed, profileObject);
- profileObject.Elements["/N"] = new PdfInteger(3);
- profileObject.Elements["/Length"] = new PdfInteger(profileCompressed.Length);
- profileObject.Elements["/Filter"] = new PdfName("/FlateDecode");
+ profileObject.Elements.SetInteger("/N", 3);
+ profileObject.Elements.SetInteger(PdfStream.Keys.Length, profileCompressed.Length);
+ profileObject.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode");
- outputIntents.Elements.Add("/DestOutputProfile", profileObject.Reference);
+ outputIntents.Elements.Add("/DestOutputProfile", profileObject.RequiredReference);
//internals.Catalog.Elements.SetReference(PdfCatalog.Keys.OutputIntents, outputIntentsArray);
internals.Catalog.Elements.Add(PdfCatalog.Keys.OutputIntents, outputIntentsArray);
}
@@ -453,25 +476,28 @@ void PrepareForPdfA() // Just a first hack.
///
/// Dispatches PrepareForSave to the objects that need it.
///
- internal override void PrepareForSave()
+ void PrepareForSave()
{
- PdfDocumentInformation info = Info;
-
- // The Creator is called 'Application' in Acrobat.
- // The Producer is call "Created by" in Acrobat.
+ // The Creator is called “Application” in Acrobat.
+ // The Producer is call “Created by” in Acrobat.
// Set Creator if value is undefined. This is the 'application' in Adobe Reader.
- if (info.Elements[PdfDocumentInformation.Keys.Creator] is null)
- info.Creator = PdfSharpProductVersionInformation.Producer;
+ //if (Info.Elements[PdfDocumentInformation.Keys.Creator] is null) // TODO #US373 Just a null check.
+ if (!Info.Elements.HasValue(PdfDocumentInformation.Keys.Creator)) // #US373
+ Info.Creator = PdfSharpProductVersionInformation.Producer;
// We set Producer if it is not yet set.
+#if true // TODO clean up
+ var pdfProducer = DefaultProducer;
+#else
var pdfProducer = PdfSharpProductVersionInformation.Creator;
#if DEBUG
// Add OS suffix only in DEBUG build.
pdfProducer += $" under {RuntimeInformation.OSDescription}";
#endif
- // Keep original producer if file was imported. This is 'PDF created by' in Adobe Reader.
- string producer = info.Producer;
+#endif
+ // Keep original producer if file was imported. This is “PDF created by” in Adobe Reader.
+ string producer = Info.Producer;
if (producer.Length == 0)
{
producer = pdfProducer;
@@ -482,7 +508,8 @@ internal override void PrepareForSave()
if (!producer.StartsWith(PdfSharpProductVersionInformation.Title, StringComparison.Ordinal))
producer = $"{pdfProducer} (Original: {producer})";
}
- info.Elements.SetString(PdfDocumentInformation.Keys.Producer, producer);
+
+ Info.Elements.SetString(PdfDocumentInformation.Keys.Producer, producer);
// Prepare used fonts.
_fontTable?.PrepareForSave();
@@ -490,19 +517,20 @@ internal override void PrepareForSave()
// Let catalog do the rest.
Catalog.PrepareForSave();
+ // #PDF-UA // TODO rewrite
+ // Create PdfMetadata now to include the final document information in XMP generation.
+ //Catalog.Elements.SetReference(PdfCatalog.Keys.Metadata, new PdfMetadata(this));
#if true
// Remove all unreachable objects (e.g. from deleted pages).
int removed = IrefTable.Compact();
if (removed != 0 && PdfSharpLogHost.Logger.IsEnabled(LogLevel.Information))
{
- PdfSharpLogHost.Logger.LogInformation($"PrepareForSave: Number of deleted unreachable objects: {removed}");
+ PdfSharpLogHost.Logger.LogInformation(
+ $"PrepareForSave: Number of deleted unreachable objects: {removed}");
}
+
IrefTable.Renumber();
#endif
-
- // #PDF-UA
- // Create PdfMetadata now to include the final document information in XMP generation.
- Catalog.Elements.SetReference(PdfCatalog.Keys.Metadata, new PdfMetadata(this));
}
///
@@ -513,9 +541,13 @@ public bool CanSave(ref string message)
if (!SecuritySettings.CanSave(ref message))
return false;
- if ((_state & DocumentState.Saved) != 0)
+ if ((State & DocumentState.Saved) != 0)
+ {
+ message = "The document was already saved and cannot be saved twice.";
return false;
+ }
+ message = "";
return true;
}
@@ -561,7 +593,10 @@ public int Version
_version = value;
}
}
- internal int _version;
+
+ internal void SetVersion(int version) => _version = version;
+
+ int _version;
///
/// Adjusts the version if the current version is lower than the required version.
@@ -591,18 +626,22 @@ public int PageCount
{
EnsureNotYetSaved();
+#if true
+ return Pages.Count;
+#else
if (CanModify)
return Pages.Count;
// PdfOpenMode is InformationOnly.
var pageTreeRoot = (PdfDictionary?)Catalog.Elements.GetObject(PdfCatalog.Keys.Pages);
return pageTreeRoot?.Elements.GetInteger(PdfPages.Keys.Count) ?? 0;
+#endif
}
}
///
/// Gets the file size of the document.
///
- public long FileSize { get; internal set; }
+ public SizeType FileSize { get; internal set; }
///
/// Gets the full qualified file name if the document was read form a file, or an empty string otherwise.
@@ -612,12 +651,12 @@ public int PageCount
///
/// Gets a Guid that uniquely identifies this instance of PdfDocument.
///
- public Guid Guid => _guid;
+ public Guid Guid { get; } = Guid.NewGuid();
- readonly Guid _guid = Guid.NewGuid();
+ //readonly Guid _guid = Guid.NewGuid();
internal DocumentHandle Handle
- => _handle ??= new DocumentHandle(this);
+ => _handle ??= new(this);
DocumentHandle? _handle;
@@ -625,25 +664,19 @@ internal DocumentHandle Handle
/// Returns a value indicating whether the document was newly created or opened from an existing document.
/// Returns true if the document was opened with the PdfReader.Open function, false otherwise.
///
- public bool IsImported => (_state & DocumentState.Imported) != 0;
+ public bool IsImported => (State & DocumentState.Imported) != 0;
///
/// Returns a value indicating whether the document is read only or can be modified.
///
- public bool IsReadOnly => (_openMode != PdfDocumentOpenMode.Modify);
-
- //internal Exception DocumentNotImported()
- //{
- // return new InvalidOperationException("Document not imported.");
- //}
+ public bool IsReadOnly => (OpenMode != PdfDocumentOpenMode.Modify);
///
/// Gets information about the document.
///
- public PdfDocumentInformation Info
- => _info ??= Trailer.Info;
+ public PdfDocumentInformation Info => _info ??= Trailer.Info;
- PdfDocumentInformation? _info; // Never changes if once created.
+ PdfDocumentInformation? _info; // Never changes if once created.
///
/// This function is intended to be undocumented.
@@ -660,15 +693,15 @@ public PdfCustomValues? CustomValues
_customValues = null;
}
}
+
PdfCustomValues? _customValues;
///
/// Get the pages dictionary.
///
- public PdfPages Pages
- => _pages ??= Catalog.Pages;
+ public PdfPages Pages => _pages ??= Catalog.Pages;
- PdfPages? _pages; // Never changes if once created.
+ PdfPages? _pages; // Never changes if once created.
///
/// Gets or sets a value specifying the page layout to be used when the document is opened.
@@ -709,9 +742,10 @@ public PdfPageMode PageMode
public PdfOutlineCollection Outlines => Catalog.Outlines;
///
- /// Get the AcroForm dictionary.
+ /// Get the AcroForm (interactive form) dictionary.
///
- public PdfAcroForm AcroForm => Catalog.AcroForm;
+ [Obsolete("Use Catalog.GetAcroForm or Catalog.GetOrCreateAcroForm.")]
+ public PdfForm AcroForm => Catalog.AcroForm;
///
/// Gets or sets the default language of the document.
@@ -727,7 +761,8 @@ public string Language
///
public PdfSecuritySettings SecuritySettings
=> _securitySettings ??= new(this);
- internal PdfSecuritySettings? _securitySettings;
+
+ PdfSecuritySettings? _securitySettings;
///
/// Adds characters whose glyphs have to be embedded in the PDF file.
@@ -753,6 +788,7 @@ public void AddCharacters(XFont font, string chars)
///
internal PdfFontTable FontTable
=> _fontTable ??= new(this);
+
PdfFontTable? _fontTable;
///
@@ -760,13 +796,15 @@ internal PdfFontTable FontTable
///
internal PdfImageTable ImageTable
=> _imageTable ??= new(this);
+
PdfImageTable? _imageTable;
///
/// Gets the document form table that holds all form external objects used in the current document.
///
- internal PdfFormXObjectTable FormTable // TODO_OLD: Rename to ExternalDocumentTable.
+ internal PdfFormXObjectTable FormTable // TODO_OLD: Rename to ExternalDocumentTable.
=> _formTable ??= new(this);
+
PdfFormXObjectTable? _formTable;
///
@@ -774,6 +812,7 @@ internal PdfImageTable ImageTable
///
internal PdfExtGStateTable ExtGStateTable
=> _extGStateTable ??= new(this);
+
PdfExtGStateTable? _extGStateTable;
///
@@ -781,24 +820,78 @@ internal PdfExtGStateTable ExtGStateTable
///
internal PdfFontDescriptorCache PdfFontDescriptorCache
=> _pdfFontDescriptorCache ??= new(this);
+
PdfFontDescriptorCache? _pdfFontDescriptorCache;
///
/// Gets the PdfCatalog of the current document.
///
- internal PdfCatalog Catalog
+ public PdfCatalog Catalog
=> _catalog ??= Trailer.Root;
- PdfCatalog? _catalog; // never changes if once created
+ PdfCatalog? _catalog; // never changes if once created
///
/// Gets the PdfInternals object of this document, that grants access to some internal structures
/// which are not part of the public interface of PdfDocument.
///
- public new PdfInternals Internals
- => _internals ??= new PdfInternals(this);
+ public PdfDocumentInternals Internals
+ => _internals ??= new PdfDocumentInternals(this);
- PdfInternals? _internals;
+ PdfDocumentInternals? _internals;
+
+ //public MetadataManager Metadata
+ // => _metadataManager ??= new MetadataManager(this);
+
+ //MetadataManager? _metadataManager;
+
+ //public PdfAManager PdfA
+ // => _pdfAManager ??= new PdfAManager(this);
+
+ //PdfAManager? _pdfAManager;
+
+ //public EmbeddedFilesManager EmbeddedFiles
+ // => _embeddedFilesManager ??= new EmbeddedFilesManager(this);
+
+ //EmbeddedFilesManager? _embeddedFilesManager;
+
+ internal DocumentState State { get; set; }
+
+ internal PdfDocumentOpenMode OpenMode { get; set; }
+
+ internal MetadataManager? MetadataManager { get; set; }
+
+ internal MetadataManager GetMetadataManager()
+ => MetadataManager ?? MetadataManager.ForDocument(this);
+
+ internal SecurityManager? SecurityManager { get; set; }
+
+ internal SecurityManager GetSecurityManager()
+ => SecurityManager ?? SecurityManager.ForDocument(this);
+
+ internal SigningManager? SigningManager { get; set; }
+
+ internal SigningManager GetSigningManager()
+ => SigningManager ?? SigningManager.ForDocument(this);
+
+ internal EmbeddedFilesManager? EmbeddedFilesManager { get; set; }
+
+ internal EmbeddedFilesManager GetEmbeddedFilesManager()
+ => EmbeddedFilesManager ?? EmbeddedFilesManager.ForDocument(this);
+
+ internal PdfAManager? PdfAManager { get; set; }
+
+ internal PdfAManager GetPdfAManager()
+ => PdfAManager ??= PdfAManager.ForDocument(this);
+
+ // ReSharper disable once InconsistentNaming
+ internal UAManager? UAManager { get; set; }
+
+ // ReSharper disable once InconsistentNaming
+ internal UAManager GetUAManager()
+ => UAManager ??= UAManager.ForDocument(this);
+
+ internal DigitalSignatureHandler? DigitalSignatureHandler { get; set; }
///
/// Creates a new page and adds it to this document.
@@ -861,7 +954,8 @@ public PdfPage InsertPage(int index, PdfPage page)
/// The Named Destination’s name.
/// The page to navigate to.
/// The PdfNamedDestinationParameters defining the named destination’s parameters.
- public void AddNamedDestination(string destinationName, int destinationPage, PdfNamedDestinationParameters parameters)
+ public void AddNamedDestination(string destinationName, int destinationPage,
+ PdfNamedDestinationParameters parameters)
=> Internals.Catalog.Names.AddNamedDestination(destinationName, destinationPage, parameters);
///
@@ -869,6 +963,7 @@ public void AddNamedDestination(string destinationName, int destinationPage, Pdf
///
/// The name used to refer and to entitle the embedded file.
/// The path of the file to embed.
+ [Obsolete("Use EmbeddedFilesManager.ForDocument(document).Add(embeddedFileInfo)")]
public void AddEmbeddedFile(string name, string path)
{
var stream = new FileStream(path, FileMode.Open);
@@ -876,24 +971,67 @@ public void AddEmbeddedFile(string name, string path)
}
///
+ /// This function is deprecated. TODO
/// Adds an embedded file to the document.
///
/// The name used to refer and to entitle the embedded file.
/// The stream containing the file to embed.
+ [Obsolete("Use EmbeddedFilesManager.ForDocument(document).Add(embeddedFileInfo)")]
public void AddEmbeddedFile(string name, Stream stream)
- => Internals.Catalog.Names.AddEmbeddedFile(name, stream);
+ {
+ var length = (int)stream.Length;
+ var bytes = new byte[length];
+ var read = stream.Read(bytes, 0, length);
+ if (read != length)
+ throw new InvalidOperationException($"Try to read {length} bytes, but read {read} bytes.");
+
+ var efi = new EmbeddedFileInfo
+ {
+ NamesKey = name,
+ FileName = name,
+ FileType = "",
+ Description = "",
+ CreationTime = DateTimeOffset.Now,
+ ModificationTime = null,
+ Data = bytes,
+ AFRelationship = PdfAFRelationship.Unspecified
+ };
+ EmbeddedFilesManager.ForDocument(this).AddFile(efi);
+ }
///
/// Flattens a document (make the fields non-editable).
///
+ [Obsolete("This function does nothing useful and was removed.")]
public void Flatten()
+ => throw new InvalidOperationException("This function does nothing useful and was removed.");
+
+ ///
+ /// TODO. Experimental, do no use.
+ ///
+ ///
+ ///
+ internal void RegisterPlugIn(IPdfSharpPlugInV0 plugIn)
{
- for (int idx = 0; idx < AcroForm.Fields.Count; idx++)
+ var key = plugIn.ID;
+ if (_plugIns.TryGetValue(key, out var item))
{
- AcroForm.Fields[idx].ReadOnly = true;
+ if (ReferenceEquals(item, plugIn))
+ return;
+ throw new InvalidOperationException("PlugIn with same ID already registered.");
}
+
+ _plugIns[key] = plugIn;
+ }
+
+ internal IPdfSharpPlugInV0? GetPlugIn(Guid id)
+ {
+ _plugIns.TryGetValue(id, out var plugIn);
+ return plugIn;
}
+ readonly Dictionary _plugIns = [];
+
///
/// Gets the standard security handler and creates it, if not existing.
///
@@ -904,16 +1042,34 @@ public void Flatten()
///
internal PdfStandardSecurityHandler? EffectiveSecurityHandler => Trailer.EffectiveSecurityHandler;
- internal PdfTrailer Trailer { get; set; } = default!;
+ internal PdfTrailer Trailer { get; set; } = null!;
- internal PdfCrossReferenceTable IrefTable { get; set; } = default!;
+ internal PdfCrossReferenceTable IrefTable { get; set; } = null!;
internal Stream? OutStream { get; set; }
// Imported Document.
internal Lexer? _lexer;
- internal DateTime _creation;
+ internal DateTimeOffset CreationDate { get; }
+
+ internal string DefaultProducer
+ {
+ get
+ {
+ if (_defaultProducer == null)
+ {
+ // We set Producer if it is not yet set.
+ _defaultProducer = PdfSharpProductVersionInformation.Creator;
+#if DEBUG
+ // Add OS suffix only in DEBUG build.
+ _defaultProducer += $" under {RuntimeInformation.OSDescription}";
+#endif
+ }
+ return _defaultProducer;
+ }
+ }
+ string? _defaultProducer;
///
/// Occurs when the specified document is not used anymore for importing content.
@@ -947,7 +1103,7 @@ internal class DocumentHandle(PdfDocument document)
readonly WeakReference _weakRef = new(document);
- public readonly string ID = document._guid.ToString("B").ToUpper();
+ public readonly string ID = document.Guid.ToString("B").ToUpper();
@@ -974,7 +1130,7 @@ public override bool Equals(object? obj)
internal void EnsureNotYetSaved()
{
- if ((_state & DocumentState.Saved) == 0)
+ if ((State & DocumentState.Saved) == 0)
return;
var message = "The document was already saved and cannot be modified anymore. " +
@@ -984,17 +1140,25 @@ internal void EnsureNotYetSaved()
throw new InvalidOperationException(message);
}
- internal DocumentState _state;
- internal PdfDocumentOpenMode _openMode;
- internal UAManager? _uaManager;
- internal DigitalSignatureHandler? _digitalSignatureHandler;
- }
+ internal void EnsureNotDisposed()
+ {
+ // TODO
+ }
-#if true_
- // UNDER_CONSTRUCTION
- static class PDFA_
- {
- public static bool IsPdfA => true;
+ // TODO: No internal fields anymore!
+ // /*internal*/
+ // DocumentState _state;
+ // /*internal*/
+ // PdfDocumentOpenMode _openMode;
+ // /*internal*/
+ // MetadataManager? _metadataManager;
+ // /*internal*/
+ // EmbeddedFilesManager? _embeddedFilesManager;
+ // /*internal*/
+ // PdfAManager? _pdfAManager;
+ // /*internal*/
+ // UAManager? _uaManager;
+ // /*internal*/
+ // DigitalSignatureHandler? _digitalSignatureHandler;
}
-#endif
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentInformation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentInformation.cs
index 7856b948..0b37aaa1 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentInformation.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentInformation.cs
@@ -1,6 +1,8 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+// v7.0.0 TODO review and sync with metadata, DateTimeOffset review
+
namespace PdfSharp.Pdf
{
///
@@ -8,13 +10,21 @@ namespace PdfSharp.Pdf
///
public sealed class PdfDocumentInformation : PdfDictionary
{
+ // Reference 2.0: 14.3.3 Document information dictionary / Page 716
+
///
/// Initializes a new instance of the class.
///
public PdfDocumentInformation(PdfDocument document)
: base(document)
- { }
+ {
+ Producer = document.DefaultProducer;
+ }
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
internal PdfDocumentInformation(PdfDictionary dict)
: base(dict)
{ }
@@ -33,12 +43,15 @@ public string Title
///
public string Author
{
+ // Field is "Creator" in XMP. "Author" from document info will be used if XMP metadata
+ // is not used. If XMP metadata is used, the field Author can be empty if Creator cannot
+ // be found in XMP; Author from document info will then be ignored.
get => Elements.GetString(Keys.Author);
set => Elements.SetString(Keys.Author, value);
}
///
- /// Gets or sets the name of the subject of the document.
+ /// Gets or sets the subject of the document.
///
public string Subject
{
@@ -67,90 +80,151 @@ public string Creator
///
/// Gets the producer application (for example, PDFsharp).
///
- public string Producer => Elements.GetString(Keys.Producer);
+ public string Producer
+ {
+ get => Elements.GetString(Keys.Producer);
+#if PDFSHARP_SET_PRODUCER
+ set => Elements.SetString(Keys.Producer, value);
+#else
+ internal init => Elements.SetString(Keys.Producer, value);
+#endif
+ }
///
- /// Gets or sets the creation date of the document.
- /// Breaking Change: If the date is not set in a PDF file DateTime.MinValue is returned.
+ /// Gets or sets the creation date of the document or null, if no entry is set.
///
- public DateTime CreationDate
+ public DateTimeOffset? CreationDate
{
- get => Elements.GetDateTime(Keys.CreationDate, DateTime.MinValue);
- set => Elements.SetDateTime(Keys.CreationDate, value);
+ get => Elements.GetDateTime(Keys.CreationDate, null);
+ set
+ {
+ if (value == null)
+ Elements.Remove(Keys.CreationDate);
+ else
+ Elements.SetDateTime(Keys.CreationDate, value.Value);
+ }
}
///
- /// Gets or sets the modification date of the document.
- /// Breaking Change: If the date is not set in a PDF file DateTime.MinValue is returned.
+ /// Gets or sets the modification date of the document or null, if no entry is set.
///
- public DateTime ModificationDate
+ public DateTimeOffset? ModificationDate
{
- get => Elements.GetDateTime(Keys.ModDate, DateTime.MinValue);
- set => Elements.SetDateTime(Keys.ModDate, value);
+ get => Elements.GetDateTime(Keys.ModDate, null);
+ set
+ {
+ if (value == null)
+ Elements.Remove(Keys.ModDate);
+ else
+ Elements.SetDateTime(Keys.ModDate, value.Value);
+ }
}
- // TODO_OLD CustomProperties and metadata
+ // TODO CustomProperties and metadata
///
/// Predefined keys of this dictionary.
///
- internal sealed class Keys : KeysBase
+ public sealed class Keys : KeysBase
{
+ // Reference 2.0: Table 349 — Entries in the document information dictionary / Page 716
+
///
- /// (Optional; PDF 1.1) The document’s title.
+ /// (Optional; PDF 1.1) The document’s title.
+ /// NOTE 1
+ /// The dc:title entry in the document’s metadata stream can be used to represent the
+ /// document’s title.
///
- [KeyInfo(KeyType.String | KeyType.Optional)]
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Title = "/Title";
///
- /// (Optional) The name of the person who created the document.
+ /// (Optional) The name of the person who created the document.
+ /// NOTE 2
+ /// The dc:creator entry in the document’s metadata stream can be used to represent the
+ /// person or persons who created the document. This note was corrected in this
+ /// document (2020).
///
- [KeyInfo(KeyType.String | KeyType.Optional)]
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Author = "/Author";
///
- /// (Optional; PDF 1.1) The subject of the document.
+ /// (Optional; PDF 1.1) The subject of the document.
+ /// NOTE 3
+ /// The dc:description entry in the document’s metadata stream can be used to represent
+ /// the subject the document.
///
- [KeyInfo(KeyType.String | KeyType.Optional)]
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Subject = "/Subject";
///
- /// (Optional; PDF 1.1) Keywords associated with the document.
+ /// (Optional; PDF 1.1) Keywords associated with the document.
+ /// NOTE 4
+ /// The pdf:Keywords entry in the document’s metadata stream can be used to represent the
+ /// keywords for the document.
///
- [KeyInfo(KeyType.String | KeyType.Optional)]
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Keywords = "/Keywords";
///
- /// (Optional) If the document was converted to PDF from another format,
- /// the name of the application (for example, empira MigraDoc) that created the
- /// original document from which it was converted.
+ /// (Optional) If the document was converted to PDF from another format, the name of the
+ /// application (for example, Adobe FrameMaker®) that created the original document from
+ /// which it was converted.
+ /// NOTE 5
+ /// The xmp:CreatorTool entry in the document’s metadata stream can be used to represent
+ /// the creation tool of the document.
///
- [KeyInfo(KeyType.String | KeyType.Optional)]
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Creator = "/Creator";
///
/// (Optional) If the document was converted to PDF from another format,
- /// the name of the application (for example, this library) that converted it to PDF.
+ /// the name of the application (for example, this library) that converted it to PDF.
+ /// NOTE 6
+ /// The pdf:Producer entry in the document’s metadata stream can be used to represent the
+ /// tool that saved the document as a PDF.
///
- [KeyInfo(KeyType.String | KeyType.Optional)]
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Producer = "/Producer";
///
- /// (Optional) The date and time the document was created, in human-readable form.
+ /// (Optional) The date and time the document was created, in human-readable form.
+ /// NOTE 7
+ /// The xmp:CreateDate entry in the document’s metadata stream can be used to represent
+ /// document’s creation date and time.
///
[KeyInfo(KeyType.Date | KeyType.Optional)]
public const string CreationDate = "/CreationDate";
///
/// (Required if PieceInfo is present in the document catalog; otherwise optional; PDF 1.1)
- /// The date and time the document was most recently modified, in human-readable form.
+ /// The date and time the document was most recently modified, in human-readable form.
+ /// NOTE 8
+ /// The xmp:ModifyDate entry in the document’s metadata stream can be used to represent the
+ /// date and time the document was most recently modified.
///
[KeyInfo(KeyType.String | KeyType.Optional)]
public const string ModDate = "/ModDate";
///
- /// (Optional; PDF 1.3) A name object indicating whether the document has been modified
- /// to include trapping information.
+ /// (Optional; PDF 1.3; deprecated in PDF 2.0) A name object indicating whether the document
+ /// has been modified to include trapping information:
+ /// True
+ /// The document has been fully trapped; no further trapping is needed. (This is the name True,
+ /// not the boolean value true.)
+ /// False
+ /// The document has not yet been trapped; any desired trapping must still be done.
+ /// (This is the name False, not the boolean value false.)
+ /// Unknown
+ /// Either it is unknown whether the document has been trapped or it has been partly but not
+ /// yet fully trapped; some additional trapping may still be needed.
+ /// Default value: Unknown.
+ /// NOTE 9
+ /// The value of this entry can be set automatically by the software creating the document’s
+ /// trapping information, or it can be known only to a human operator and entered manually.
+ /// NOTE 10
+ /// The pdf:Trapped entry in the document’s metadata stream can be used to represent the
+ /// trapping information for the document.
///
[KeyInfo("1.3", KeyType.Name | KeyType.Optional)]
public const string Trapped = "/Trapped";
@@ -158,7 +232,7 @@ internal sealed class Keys : KeysBase
///
/// Gets the KeysMeta for these keys.
///
- public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
+ internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys));
static DictionaryMeta? _meta;
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs
index fcc8df71..f1287462 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs
@@ -3,6 +3,8 @@
// ReSharper disable ConvertToAutoProperty
+using PdfSharp.Pdf.IO;
+
namespace PdfSharp.Pdf
{
///
@@ -81,5 +83,21 @@ public PdfUseFlateDecoderForJpegImages UseFlateDecoderForJpegImages
set => _useFlateDecoderForJpegImages = value;
}
PdfUseFlateDecoderForJpegImages _useFlateDecoderForJpegImages = PdfUseFlateDecoderForJpegImages.Never;
+
+ ///
+ /// Gets or sets a value used for the PdfWriterLayout in PdfWriter.
+ ///
+ public PdfWriterLayout Layout
+ {
+ get => _writerLayout;
+ set => _writerLayout = value;
+ }
+
+ PdfWriterLayout _writerLayout =
+#if DEBUG
+ PdfWriterLayout.Verbose;
+#else
+ PdfWriterLayout.Compact;
+#endif
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs
index 687e98b7..12159322 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs
@@ -15,20 +15,34 @@ public sealed class PdfInteger : PdfNumber, IConvertible
/// Initializes a new instance of the class.
///
public PdfInteger()
- {
- IsInteger = true;
- }
+ => IsInteger = true;
///
/// Initializes a new instance of the class.
///
/// The value.
- public PdfInteger(int value)
+ public PdfInteger(int value) : this()
+ => Value = value;
+
+ internal PdfInteger(int value, bool isFlag) : this()
{
- IsInteger = true;
Value = value;
+ IsFlag = isFlag;
}
+#if PRESERVE_PARSED_VALUES
+ internal PdfInteger(int value, string parsedValue, bool isFlag) : this()
+ {
+ Value = value;
+ ParsedValue = parsedValue;
+ IsFlag = isFlag;
+ }
+#endif
+ ///
+ /// Gets or sets a value indicating whether this instance is used as a 32-bt flag.
+ ///
+ public bool IsFlag { get; set; }
+
///
/// Gets the value as integer.
///
@@ -38,7 +52,11 @@ public PdfInteger(int value)
/// Returns the integer as string.
///
public override string ToString()
+#if PRESERVE_PARSED_VALUES
+ => ParsedValue ?? Value.ToString(CultureInfo.InvariantCulture);
+#else
=> Value.ToString(CultureInfo.InvariantCulture);
+#endif
///
/// Writes the integer as string.
@@ -58,8 +76,6 @@ double IConvertible.ToDouble(IFormatProvider? provider)
=> Value;
DateTime IConvertible.ToDateTime(IFormatProvider? provider)
- //// TO-DO: Add PdfInteger.ToDateTime implementation
- // => new DateTime();
=> throw new InvalidCastException();
float IConvertible.ToSingle(IFormatProvider? provider)
@@ -99,10 +115,7 @@ decimal IConvertible.ToDecimal(IFormatProvider? provider)
=> Value;
object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
- {
- // TODO_OLD: Add PdfInteger.ToType implementation
- return null!;
- }
+ => throw new NotImplementedException("Conversion not implemented.");
uint IConvertible.ToUInt32(IFormatProvider? provider)
=> Convert.ToUInt32(Value);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfIntegerObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfIntegerObject.cs
index c2f6f6fa..0a727b28 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfIntegerObject.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfIntegerObject.cs
@@ -6,8 +6,8 @@
namespace PdfSharp.Pdf
{
///
- /// Represents an indirect 32-bit signed integer value. This type is not used by PDFsharp. If it is imported from
- /// an external PDF file, the value is converted into a direct object.
+ /// Represents an indirect 32-bit signed integer value. This type is not created by PDFsharp.
+ /// If it is imported from an external PDF file, the value is converted into a direct object.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
public sealed class PdfIntegerObject : PdfNumberObject
@@ -16,22 +16,36 @@ public sealed class PdfIntegerObject : PdfNumberObject
/// Initializes a new instance of the class.
///
public PdfIntegerObject()
- { }
+ => IsInteger = true;
///
/// Initializes a new instance of the class.
///
- public PdfIntegerObject(int value)
+ public PdfIntegerObject(int value) : this()
+ => Value = value;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfIntegerObject(PdfDocument document, int value)
+ : base(document, true)
{
+ IsInteger = true;
Value = value;
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class
+ /// without making it indirect.
+ /// Used in PDF parser only.
///
- public PdfIntegerObject(PdfDocument document, int value)
- : base(document)
+ /// The document.
+ /// The initial value.
+ /// If true creates an indirect object.
+ internal PdfIntegerObject(PdfDocument document, int value, bool createIndirect)
+ : base(document, createIndirect)
{
+ IsInteger = true;
Value = value;
}
@@ -43,7 +57,7 @@ public PdfIntegerObject(PdfDocument document, int value)
///
/// Returns the integer as string.
///
- public override string ToString()
+ public override string ToString()
=> Value.ToString(CultureInfo.InvariantCulture);
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs
index 0a4e3f90..b6684d27 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs
@@ -2,37 +2,194 @@
// See the LICENSE file in the solution root for more information.
using PdfSharp.Pdf.IO;
+using System.Runtime.CompilerServices;
+
+#if PDFSHARP_DEBUG
+using static PdfSharp.Diagnostics.DebugBreakHelper;
+#endif
namespace PdfSharp.Pdf
{
///
- /// The base class of all PDF objects and simple PDF types.
+ /// The base class of all PDF objects and primitive PDF types.
///
public abstract class PdfItem : ICloneable
{
- // All simple types (i.e. derived from PdfItem but not from PdfObject) must be immutable.
+ ///
+ /// Initialized a new instance of this class.
+ ///
+ protected PdfItem()
+ {
+#if PDFSHARP_DEBUG
+ InitItemNumber();
+#endif
+ ItemFlags = ItemFlags.IsPrimitiveItem;
+ }
- object ICloneable.Clone()
+ ///
+ /// Initialized a new instance of this class.
+ ///
+ protected PdfItem(PdfItem item)
{
- return Copy();
+#if PDFSHARP_DEBUG
+ InitItemNumber();
+#endif
+ ItemFlags = item.ItemFlags;
}
+ object ICloneable.Clone() => Copy();
+
///
/// Creates a copy of this object.
///
- public PdfItem Clone()
- => (PdfItem)Copy();
+ public PdfItem Clone() => (PdfItem)Copy();
///
/// Implements the copy mechanism. Must be overridden in derived classes.
///
- protected virtual object Copy()
- => MemberwiseClone();
+#if !PDFSHARP_DEBUG
+ protected virtual object Copy() => MemberwiseClone();
+#else
+ protected virtual object Copy()
+ {
+ var item = (PdfItem)MemberwiseClone();
+ InitItemNumber();
+ return item;
+ }
+#endif
///
/// When overridden in a derived class, appends a raw string representation of this object
/// to the specified PdfWriter.
///
internal abstract void WriteObject(PdfWriter writer);
+
+ ///
+ /// Some low-level flags for making the code more efficient.
+ ///
+ internal ItemFlags ItemFlags;
+
+ ///
+ /// Is PdfItem but not PdfObject.
+ ///
+ internal bool IsPureItem => (ItemFlags & ItemFlags.IsPrimitiveItem) is not 0;
+
+ ///
+ /// Is PdfObject, but not PdfArray or PdfDictionary.
+ ///
+ internal bool IsPureObject => (ItemFlags & ItemFlags.IsCompoundObject) is not 0;
+
+ ///
+ /// Is PdfArray.
+ ///
+ internal bool IsArray => (ItemFlags & ItemFlags.IsArray) is not 0;
+
+ ///
+ /// Is PdfDictionary.
+ ///
+ internal bool IsDictionary => (ItemFlags & ItemFlags.IsDictionary) is not 0;
+
+ ///
+ /// Is PdfArray or PdfDictionary.
+ ///
+ internal bool IsArrayOrDictionary => (ItemFlags & ItemFlags.IsArrayOrDictionary) is not 0;
+
+ internal bool ShouldTryTransformation => (ItemFlags & ItemFlags.TransformationMask) is 0;
+
+ internal bool IsTransformed => (ItemFlags & ItemFlags.IsTransformed) is not 0;
+
+ internal void SetTransformed()
+ {
+#if PDFSHARP_DEBUG
+ if (ShouldBreak5)
+ Debugger.Break();
+#endif
+ ItemFlags |= ItemFlags.IsTransformed;
+ }
+
+ internal void SetTransformationTried()
+ {
+ //ItemFlags &= ItemFlags.ShTransformationWasTried;
+ ItemFlags |= ItemFlags.TransformationWasTried;
+ }
+
+ internal void SetMustBeIndirect()
+ {
+#if PDFSHARP_DEBUG
+ if (ShouldBreak1)
+ Debugger.Break();
+#endif
+ ItemFlags |= ItemFlags.MustBeIndirect;
+ }
+
+ internal bool MustBeIndirect()
+ {
+#if PDFSHARP_DEBUG
+ if (ShouldBreak1)
+ Debugger.Break();
+#endif
+ return (ItemFlags & ItemFlags.MustBeIndirect) is not 0;
+ }
+
+ ///
+ /// Gets a value indicating whether a PDF object dead after an
+ /// object type transformation.
+ ///
+ internal bool IsDead
+ {
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get { return (ItemFlags & ItemFlags.IsDead) is not 0; }
+ }
+
+ ///
+ /// Marks a PDF object as dead.
+ ///
+ internal void SetDead()
+ {
+ Debug.Assert(this is PdfContainer);
+#if DEBUG_
+ //if (this is PdfObject { ObjectNumber: 96049 })
+ // _ = typeof(int);
+
+ //if (ShouldBreak1)
+ // Debugger.Break();
+ if (this is PdfArray array)
+ {
+ if (array.Count() == 4)
+ Debugger.Break();
+ }
+
+#endif
+#if PDFSHARP_DEBUG
+ PdfSharpDebug.Instance.AddDeadContainer((PdfContainer)this);
+#endif
+ ItemFlags |= ItemFlags.IsDead;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void EnsureAlive()
+ {
+ if (IsDead)
+ {
+ throw new InvalidOperationException(
+ $"This instance of {GetType().FullName} cannot be used anymore, because it is dead. " +
+ "This happens when a PDF object is transformed to a derived class and your code still holds " +
+ "a reference to the old instance. This was wrong all along but is detected now.");
+ }
+ }
+
+#if PDFSHARP_DEBUG
+ void InitItemNumber()
+ {
+ ItemNumber = ++_itemCounter;
+ }
+
+ ///
+ /// Gets the unique item count used for debugging purposes.
+ ///
+ public int ItemNumber { get; private set; } = 0;
+
+ static int _itemCounter = 0;
+#endif
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItemExtensions/PdfItemExtensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItemExtensions/PdfItemExtensions.cs
new file mode 100644
index 00000000..6677e8b2
--- /dev/null
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItemExtensions/PdfItemExtensions.cs
@@ -0,0 +1,141 @@
+// PDFsharp - A .NET library for processing PDF
+// See the LICENSE file in the solution root for more information.
+
+using PdfSharp.Pdf.Advanced;
+
+namespace PdfSharp.Pdf.PdfItemExtensions
+{
+ ///
+ /// Extension methods for PDF items.
+ ///
+ public static class PdfItemExtensions
+ {
+
+ ///
+ /// Casts a PDF item into a PDF array, or throws an exception if this is not possible.
+ ///
+ public static PdfArray AsArray(this PdfItem? value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (value is PdfReference reference)
+ value = reference.Value;
+
+ if (value is PdfArray arr)
+ return arr;
+
+ throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to PdfArray.");
+ }
+
+ ///
+ /// Casts a PDF item into a PDF array of type T, or throws an exception if this is not possible.
+ ///
+ public static T AsArray(this PdfItem? value) where T : PdfArray
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (value is PdfReference reference)
+ value = reference.Value;
+
+ if (value is T arr)
+ return arr;
+
+ throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to {typeof(T).FullName}.");
+ }
+
+ //public static PdfArray AsArray(this PdfReference? value)
+ //{
+ // if (value == null)
+ // throw new ArgumentNullException(nameof(value));
+
+ // if (value.Value is PdfArray arr)
+ // return arr;
+
+ // throw new InvalidCastException("value is not a PdfArray.");
+ //}
+
+ //public static T AsArray(this PdfReference? value) where T : PdfArray
+ //{
+ // if (value == null)
+ // throw new ArgumentNullException(nameof(value));
+
+ // if (value.Value is T arr)
+ // return arr;
+
+ // throw new InvalidCastException($"value is not a {typeof(T).Name}.");
+ //}
+
+ ///
+ /// Casts a PDF item into a PDF dictionary of type T, or throws an exception if this is not possible.
+ ///
+ public static PdfDictionary AsDictionary(this PdfItem? value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (value is PdfReference reference)
+ value = reference.Value;
+
+ if (value is PdfDictionary dict)
+ return dict;
+
+ throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to PdfDictionary.");
+ }
+
+ ///
+ /// Casts a PDF item into a PDF dictionary of type T, or throws an exception if this is not possible.
+ ///
+ public static T AsDictionary(this PdfItem? value) where T : PdfDictionary
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (value is PdfReference reference)
+ value = reference.Value;
+
+ if (value is T dict)
+ return dict;
+
+ throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to '{typeof(T).FullName}'.");
+ }
+
+ //public static PdfDictionary AsDictionary(this PdfReference? value)
+ //{
+ // if (value == null)
+ // throw new ArgumentNullException(nameof(value));
+
+ // if (value.Value is PdfDictionary dict)
+ // return dict;
+
+ // throw new InvalidCastException("value is not a PdfDictionary.");
+ //}
+
+ //public static T AsDictionary(this PdfReference? value) where T : PdfDictionary
+ //{
+ // if (value == null)
+ // throw new ArgumentNullException(nameof(value));
+
+ // if (value.Value is T dict)
+ // return dict;
+
+ // throw new InvalidCastException($"value is not a {typeof(T).Name}.");
+ //}
+
+ ///
+ /// Casts a PDF item into a PDF reference, or throws an exception if this is not possible.
+ ///
+ public static PdfReference AsReference(this PdfItem? value)
+ {
+ if (value == null)
+ throw new ArgumentNullException(nameof(value));
+
+ if (value is PdfReference reference)
+ return reference;
+
+ throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to PdfReference.");
+ }
+ }
+}
+
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs
index 0526c96a..6f675533 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs
@@ -1,4 +1,4 @@
-// PDFsharp - A .NET library for processing PDF
+// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
using PdfSharp.Drawing;
@@ -8,19 +8,21 @@
namespace PdfSharp.Pdf
{
///
- /// Represents text that is written 'as it is' into the PDF stream. This class can lead to invalid PDF files.
+ /// Represents text that is written ‘as it is’ into the PDF stream.
+ /// Using this class can lead to invalid PDF files.
/// E.g. strings in a literal are not encrypted when the document is saved with a password.
///
- public sealed class PdfLiteral : PdfItem
+ public sealed class PdfLiteral : PdfPrimitive
{
- ///
- /// Initializes a new instance of the class.
- ///
- public PdfLiteral()
- { }
+ // Note that PdfLiteral is used by PDFsharp to have an easy way to write e.g. a matrix or a
+ // page destination by creating just a single item instead of creating arrays with items.
+ // By contrast, PdfDebugItem and PdfDebugObject are used only in unit tests to create
+ // illegal PDF content.
///
/// Initializes a new instance with the specified string.
+ /// The string is (as always) interpreted as an UTF16 .NET string and written
+ /// as a raw string.
///
public PdfLiteral(string value)
{
@@ -38,6 +40,7 @@ public PdfLiteral(string format, params object[] args)
///
/// Creates a literal from an XMatrix
///
+ [Obsolete]
public static PdfLiteral FromMatrix(XMatrix matrix)
{
return new PdfLiteral($"[{PdfEncoders.ToString(matrix)}]");
@@ -46,14 +49,14 @@ public static PdfLiteral FromMatrix(XMatrix matrix)
///
/// Gets the value as literal string.
///
- public string Value { get; } = "";
+ public string Value { get; }
///
/// Returns a string that represents the current value.
///
public override string ToString() => Value;
- internal override void WriteObject(PdfWriter writer)
+ internal override void WriteObject(PdfWriter writer)
=> writer.Write(this);
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongInteger.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongInteger.cs
index 59f86a62..faf6a32b 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongInteger.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongInteger.cs
@@ -14,20 +14,15 @@ public sealed class PdfLongInteger : PdfNumber, IConvertible
///
/// Initializes a new instance of the class.
///
- public PdfLongInteger()
- {
- IsLongInteger = true;
- }
+ public PdfLongInteger()
+ => IsLongInteger = true;
///
/// Initializes a new instance of the class.
///
/// The value.
- public PdfLongInteger(long value)
- {
- IsLongInteger = true;
- Value = value;
- }
+ public PdfLongInteger(long value):this()
+ => Value = value;
///
/// Gets the value as 64-bit integer.
@@ -58,8 +53,6 @@ double IConvertible.ToDouble(IFormatProvider? provider)
=> Value;
DateTime IConvertible.ToDateTime(IFormatProvider? provider)
- //// TO-DO: Add PdfInteger.ToDateTime implementation
- // => new DateTime();
=> throw new InvalidCastException();
float IConvertible.ToSingle(IFormatProvider? provider)
@@ -98,11 +91,8 @@ public TypeCode GetTypeCode()
decimal IConvertible.ToDecimal(IFormatProvider? provider)
=> Value;
- object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
- {
- // TODO_OLD: Add PdfInteger.ToType implementation
- return null!;
- }
+ object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
+ => throw new NotImplementedException("Conversion not implemented.");
uint IConvertible.ToUInt32(IFormatProvider? provider)
=> Convert.ToUInt32(Value);
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongIntegerObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongIntegerObject.cs
index 028c9ed7..a34f3f35 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongIntegerObject.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongIntegerObject.cs
@@ -6,8 +6,8 @@
namespace PdfSharp.Pdf
{
///
- /// Represents an indirect 64-bit signed integer value. This type is not used by PDFsharp. If it is imported from
- /// an external PDF file, the value is converted into a direct object.
+ /// Represents an indirect 64-bit signed integer value. This type is not created by PDFsharp.
+ /// If it is imported from an external PDF file, the value is converted into a direct object.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
public sealed class PdfLongIntegerObject : PdfNumberObject
@@ -16,22 +16,36 @@ public sealed class PdfLongIntegerObject : PdfNumberObject
/// Initializes a new instance of the class.
///
public PdfLongIntegerObject()
- { }
+ => IsLongInteger = true;
///
/// Initializes a new instance of the class.
///
- public PdfLongIntegerObject(long value)
+ public PdfLongIntegerObject(long value) : this()
+ => Value = value;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfLongIntegerObject(PdfDocument document, long value)
+ : base(document, true)
{
+ IsLongInteger = true;
Value = value;
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class
+ /// without making it indirect.
+ /// Used in PDF parser only.
///
- public PdfLongIntegerObject(PdfDocument document, long value)
- : base(document)
+ /// The document.
+ /// The initial value.
+ /// If true creates an indirect object.
+ internal PdfLongIntegerObject(PdfDocument document, long value, bool createIndirect)
+ : base(document, createIndirect)
{
+ IsLongInteger = true;
Value = value;
}
@@ -43,7 +57,7 @@ public PdfLongIntegerObject(PdfDocument document, long value)
///
/// Returns the integer as string.
///
- public override string ToString()
+ public override string ToString()
=> Value.ToString(CultureInfo.InvariantCulture);
///
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs
index 9bbef518..88d65d08 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs
@@ -1,9 +1,51 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using System;
-using System.Text;
using PdfSharp.Pdf.Internal;
+using System.Text;
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
+using PdfSharp.Pdf.Metadata;
+
+// v7.0.0 TODO review and sync with document information, DateTimeOffset review
+// TODO DocumentID, InstanceID
+
+///
+/// Superfluous implementation. XMP in PDF files must use only UTF-8 encoding.
+/// We keep it for internal tests.
+///
+enum MetadataEncodingType
+{
+ // ReSharper disable InconsistentNaming
+
+ ///
+ /// Encodes metadata stream using UTF-8 encoding.
+ /// This is the default and the only recommended option.
+ ///
+ UTF8,
+
+ ///
+ /// Encodes metadata stream using UTF-16 little-endian encoding.
+ ///
+ UTF16LE,
+
+ ///
+ /// Encodes metadata stream using UTF-16 big-endian encoding.
+ ///
+ UTF16BE,
+
+ ///
+ /// Encodes metadata stream using UTF-32 little-endian encoding.
+ ///
+ UTF32LE,
+
+ ///
+ /// Encodes metadata stream using UTF-32 big-endian encoding.
+ ///
+ UTF32BE,
+
+ // ReSharper restore InconsistentNaming
+}
namespace PdfSharp.Pdf
{
@@ -12,14 +54,14 @@ namespace PdfSharp.Pdf
///
public sealed class PdfMetadata : PdfDictionary
{
+ // Reference 2.0: 14.3.2 Metadata streams / Page 714
+
///
/// Initializes a new instance of the class.
///
public PdfMetadata()
{
- Elements.SetName(Keys.Type, "/Metadata");
- Elements.SetName(Keys.Subtype, "/XML");
- SetupStream();
+ Initialize();
}
///
@@ -27,209 +69,185 @@ public PdfMetadata()
///
/// The document that owns this object.
public PdfMetadata(PdfDocument document)
- : base(document)
+ : base(document, true)
{
- document.Internals.AddObject(this);
- Elements.SetName(Keys.Type, "/Metadata");
- Elements.SetName(Keys.Subtype, "/XML");
- SetupStream();
+ Initialize();
}
- void SetupStream()
- {
- const string begin = @"begin=""";
-
- var stream = GenerateXmp();
-
- // Preserve "" if text is UTF8 encoded.
- var i = stream.IndexOf(begin, StringComparison.Ordinal);
- var pos = i + begin.Length;
- stream = stream[..pos] + "xxx" + stream[(pos + 3)..];
-
- byte[] bytes = Encoding.UTF8.GetBytes(stream);
- bytes[pos++] = (byte)'ï';
- bytes[pos++] = (byte)'»';
- bytes[pos] = (byte)'¿';
+ ///
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
+ ///
+ internal PdfMetadata(PdfDictionary dictionary)
+ : base(dictionary)
+ { }
- CreateStream(bytes);
- }
- string GenerateXmp()
+ void Initialize()
{
- var instanceId = Guid.NewGuid().ToString();
- var documentId = Guid.NewGuid().ToString();
-
- static DateTime SpecifyLocalDateTimeKindIfUnspecified(DateTime value)
- => value.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(value, DateTimeKind.Local) : value;
+ Elements.SetName(Keys.Type, "/Metadata");
+ Elements.SetName(Keys.Subtype, "/XML");
+ }
- var creationDate = SpecifyLocalDateTimeKindIfUnspecified(_document.Info.CreationDate).ToString("yyyy-MM-ddTHH:mm:ssK");
- var modificationDate = creationDate;
+ ///
+ /// Obsolete, use ToString.
+ ///
+ [Obsolete("Use ToString.")]
+ public string Xml
+ => Stream == null ? "" : Stream.ToString();
- var author = _document.Info.Author;
- var creator = _document.Info.Creator;
- var producer = _document.Info.Producer;
- var title = _document.Info.Title;
- var subject = _document.Info.Subject;
- var keywords = _document.Info.Keywords;
+ ///
+ /// Creates the XMP metadata for the PDF document.
+ /// Creating XMP metadata is not the business of PDFsharp.
+ /// This is a suggestion how you can do it.
+ ///
+ //
+ // .NET contains classes to create XML content. We use simple text substitution here.
+ //
+ public string CreateDefaultMetadata() => CreateDefaultMetadata(MetadataEncodingType.UTF8);
- // #PDF-A Tag PDF as PDF/A-1A conform.
- string? pdfA = null;
- if (_document.IsPdfA)
- {
- // #PDF-A
- pdfA = $"""
-
- 1
- A
-
- """;
- }
-#if true
- // Created based on a PDF created with Microsoft Word.
- var str = $"""
-
-
-
-
- {producer}{keywords}
-
-
- {title}
- {author}
- {subject}
-
-
- {creator}
- {creationDate}
- {modificationDate}
-
-
- uuid:{documentId}
- uuid:{instanceId}
-
- {pdfA}
-
-
-
- """;
-#else
- // Does not exist anymore.
- // XMP Documentation: http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMP%20SDK%20Release%20cc-2016-08/XMPSpecificationPart1.pdf
-
- var str =
- // UTF-8 Byte order mark "" and GUID (like in Reference) to avoid accidental usage in data stream.
- "\n" +
-
- " \n" +
- " \n" +
- " \n" +
-
- " uuid:" + instanceId + "\n" +
- " uuid:" + documentId + "\n" +
-
- " \n" +
- " \n" +
- " 1\n" +
- " \n" +
- " \n" +
-
- " " + creationDate + "\n" +
- " " + modificationDate + "\n" +
- " " + creator + "\n" +
- " " + modificationDate + "\n" +
-
- " \n" +
- " \n" +
-
- " " + producer + "\n" +
-
- " \n" +
- " \n" +
- " \n" +
- " \n" +
-
- " " + title + "\n" +
-
- " \n" +
- " \n" +
- " \n" +
- " \n" +
- " \n" +
- "\n";
+ // I wrote the code, and we will keep it for reference.
+ internal string CreateDefaultMetadata(MetadataEncodingType encodingType)
+ {
+ // See XMP SPECIFICATION PART 1 - 3 for details.
+ // The files names are “XMPSpecificationPart1.pdf”, “XMPSpecificationPart2.pdf”, and “XMPSpecificationPart3.pdf”.
+ // They are created by Adobe in 2008 and can still be found by Google.
+ //
+ // I just scanned the specs und overlooked the fact that XMP data can be written in 5 encodings,
+ // BUT for PDF files only UTF-8 is allowed. Acrobat also accepts UFT-16 little and big endian,
+ // but not UTF-32. I wasted hours to make it work for UTF-32
+ //
+ // Now we have the code created and PDFsharp would accept all encodings, but writes only UTF-8.
+
+ var metadataInfo = MetadataManager.GetMetadataInfo();
+ // ReSharper disable StringLiteralTypo because we deal with XML elements here.
+ bool isPdfA = metadataInfo.PdfAFormat.HasValue;
+
+ var xml =
+ // XMP header.
+ // Note that in a .NET string the BOM is represented as a human-readable string.
+ // PDFsharp converts this string to correct BOM when encoded.
+ $"""
+
+
+
+ """ + "\r\n" +
+ // PDF/A section.
+ (isPdfA ?
+ $"""
+
+ {metadataInfo.PdfAFormat?.Part}
+ {metadataInfo.PdfAFormat?.ConformanceLevel}
+
+ """ + "\r\n"
+ : ""
+ ) +
+ // xmlns:pdf: Producer, Keywords
+ $"""
+
+ {metadataInfo.Producer}
+ {metadataInfo.Keywords}
+
+ """ + "\r\n" +
+ // xmlns:xap: CreatorTool, CreateDate, ModifyDate
+ $"""
+
+ {metadataInfo.Creator}
+ {metadataInfo.CreationDate}
+ {metadataInfo.ModificationDate}
+ {(!String.IsNullOrWhiteSpace(metadataInfo.ModificationDate) ? metadataInfo.ModificationDate : metadataInfo.CreationDate)}
+
+ """ + "\r\n" +
+ // xmlns:dc: Title, Creator (author), Description
+ $"""
+
+
+
+ {metadataInfo.Title}
+
+
+
+
+ {metadataInfo.Author}
+
+
+
+
+ {metadataInfo.Subject}
+
+
+
+ """ + "\r\n" +
+#if true_
+ // TODO: what if string from trailer is not a UUID???
+ // xmlns:xapMM: DocumentID, InstanceID
+ $"""
+
+ uuid:{metadataInfo.DocumentID}
+ uuid:{metadataInfo.InstanceID}
+
+ """ + "\r\n" +
#endif
+ // XMP trailer.
+ """
+
+
+
+ """;
+ // ReSharper restore StringLiteralTypo
+ return xml;
+ }
+
+ ///
+ /// Converts the stream content to a string.
+ /// The BOM in “<?xpacket begin="{BOM}"” is set to “UTF-8” because a UTF-8 BOM cannot be a
+ /// part of a Unicode string.
+ ///
+ public override String ToString() => StreamToString();
- return str;
+ ///
+ /// Replaces the stream content with the specified byte array.
+ ///
+ ///
+ public void SetMetadata(byte[] xml)
+ {
+ //var bytes = PdfEncoders.RawEncoding.GetBytes(xml);
+ Stream = null;
+ CreateStream(xml);
}
- void Foo()
+ ///
+ /// Sets the metadata stream.
+ /// The BOM tag of the “<?xpacket begin="{BOM}"” attribute must either be empty or 'UTF-8'.
+ ///
+ public void SetMetadata(string xml)
{
- var documentId = Guid.NewGuid().ToString();
- var instanceId = Guid.NewGuid().ToString();
-
- static DateTime SpecifyLocalDateTimeKindIfUnspecified(DateTime value)
- => value.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(value, DateTimeKind.Local) : value;
-
- var creationDate = SpecifyLocalDateTimeKindIfUnspecified(_document.Info.CreationDate).ToString("yyyy-MM-ddTHH:mm:ssK");
- var modificationDate = creationDate;
-
- var author = _document.Info.Author;
- var creator = _document.Info.Creator;
- var producer = _document.Info.Producer;
- var title = _document.Info.Title;
- var subject = _document.Info.Subject;
-
- string s2 = $"""
-
-
-
-
-
- {producer}
- Tag1 Tag 2 Tag3
-
-
-
-
-
- {title}
-
-
-
-
- {author}
-
-
-
-
- {subject}
-
-
-
-
-
- {creator}
- {creationDate}
- {modificationDate}
-
-
-
- uuid:{documentId}
- uuid:{instanceId}
-
-
-
-
-
- """;
+ var bytes = MetadataEncoder.GetBytes(xml);
+ SetMetadata(bytes);
}
+ ///
+ /// Converts the byte array of the stream’s value to a regular Unicode .NET string and
+ /// replaces the BOM by a string that shows the original in hexadecimal notation.
+ ///
+ string StreamToString() => MetadataEncoder.GetString(Stream?.UnfilteredValue ?? []);
+
+ MetadataManager MetadataManager => _metadataManager ??= MetadataManager.ForDocument(Document
+ ?? throw new InvalidOperationException("PdfMetadata must belong to a document to create metadata."));
+ MetadataManager? _metadataManager;
+
+ // TODO: Table 348 — Additional entry for components having metadata
+
///
/// Predefined keys of this dictionary.
///
- internal class Keys : KeysBase
+ public class Keys : KeysBase
{
+ // Reference 2.0: Table 347 — Additional entries in a metadata stream dictionary / Page 713
+
///
- /// (Required) The type of PDF object that this dictionary describes; must be Metadata for a metadata stream.
+ /// (Required) The type of PDF object that this dictionary describes; must be Metadata
+ /// for a metadata stream.
///
[KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Metadata")]
public const string Type = "/Type";
@@ -240,5 +258,314 @@ internal class Keys : KeysBase
[KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "XML")]
public const string Subtype = "/Subtype";
}
+
+ internal static class MetadataPreparer // TODO StL Eliminate this class.
+ {
+ public static void PrepareDocument(PdfDocument doc)
+ {
+ //doc.Catalog.GetMetadata();
+ }
+ }
+
+ static class MetadataEncoder
+ {
+ /* For reference see XMP SPECIFICATION PART 3 page 14
+ *
+ * Search for “ “” // Only valid BOM for XMP metadata in a PDF file.
+ const string utf16BomLE = "\u00FF\u00FE"; // <=> “ÿþ” // \
+ const string utf16BomBE = "\u00FE\u00FF"; // <=> “þÿ” // \ Valid BOMs for XMP metadata, but not in a PDF file.
+ const string utf32BomLE = "\u00FF\u00FE\0\0"; // <=> “ÿþ\0\0” // /
+ const string utf32BomBE = "\0\0\u00FE\u00FF"; // <=> “\0\0þÿ” // /
+
+ // ReSharper restore InconsistentNaming
+ // ReSharper restore StringLiteralTypo
+
+ public static string GetString(byte[] bytes)
+ {
+ // Convert stream bytes into raw string for easier coding.
+ var rawString = PdfEncoders.RawEncoding.GetString(bytes);
+ if (String.IsNullOrEmpty(rawString))
+ return "";
+
+ string result;
+ int pos;
+ if ((pos = rawString.IndexOf(utf8Header, StringComparison.Ordinal)) != -1)
+ {
+ if (pos > 0)
+ rawString = rawString[pos..];
+
+ // ReSharper disable once CommentTypo for better readability
+ // “ 0)
+ rawString = rawString[pos..];
+
+ const int leftPartLength = 2 * 17;
+ const int rightPartStart = leftPartLength + 2;
+ char leftQuot = rawString[leftPartLength - 2];
+ char rightQuot = rawString[rightPartStart];
+ if (leftQuot is '\"' or '\'' && rawString[leftPartLength..rightPartStart] == utf16BomLE && rightQuot is '\"' or '\'')
+ {
+ // Remove UTF-16LE BOM.
+ rawString = rawString[..leftPartLength] + rawString[rightPartStart..];
+ }
+ else
+ throw new InvalidOperationException("An UTF-16 encoded metadata stream has an invalid BOM entry.");
+
+ // Get bytes from stream without BOM.
+ bytes = PdfEncoders.RawEncoding.GetBytes(rawString);
+ result = Encoding.Unicode.GetString(bytes);
+ // Insert a readable encoding name.
+ result = result[..17] + MetadataEncodingType.UTF16LE + result[17..];
+ }
+ else if ((pos = rawString.IndexOf(utf16HeaderBE, StringComparison.Ordinal)) != -1)
+ {
+ PdfSharpLogHost.Logger.LogWarning("XMP metadata is encoded using UTF-16 big endian, which is not allowed in PDF files, but accepted by PDFsharp.");
+
+ if (pos > 0)
+ rawString = rawString[pos..];
+
+ const int leftPartLength = 2 * 17 + 1;
+ const int rightPartStart = leftPartLength + 2;
+ char leftQuot = rawString[leftPartLength - 2];
+ char rightQuot = rawString[rightPartStart];
+ if (leftQuot is '\"' or '\'' && rawString[(leftPartLength - 1)..(rightPartStart - 1)] == utf16BomBE && rightQuot is '\"' or '\'')
+ {
+ // Remove UTF-16BE BOM.
+ rawString = rawString[..(leftPartLength - 1)] + rawString[(rightPartStart - 1)..];
+ }
+ else
+ throw new InvalidOperationException("An UTF-16 big-endian encoded metadata stream has an invalid BOM entry.");
+
+ // Get bytes from stream without BOM.
+ bytes = PdfEncoders.RawEncoding.GetBytes(rawString);
+ result = Encoding.BigEndianUnicode.GetString(bytes);
+ // Insert a readable encoding name.
+ result = result[..17] + MetadataEncodingType.UTF16BE + result[17..];
+ }
+ else if ((pos = rawString.IndexOf(utf32HeaderLE, StringComparison.Ordinal)) is not (-1 or 3)) // 3 because utf32HeaderBE also matches here.
+ {
+ PdfSharpLogHost.Logger.LogWarning("XMP metadata is encoded using UTF-32 little endian, which is not allowed in PDF files, but accepted by PDFsharp.");
+
+ if (pos > 0)
+ rawString = rawString[pos..];
+
+ const int leftPartLength = 4 * 17;
+ const int rightPartStart = leftPartLength + 4;
+ char leftQuot = rawString[leftPartLength - 4];
+ char rightQuot = rawString[rightPartStart];
+ if (leftQuot is '\"' or '\'' && rawString[leftPartLength..rightPartStart] == utf32BomLE && rightQuot is '\"' or '\'')
+ {
+ // Remove UTF-32LE BOM.
+ rawString = rawString[..leftPartLength] + rawString[rightPartStart..];
+ }
+ else
+ throw new InvalidOperationException("An UTF-32 encoded metadata stream has an invalid BOM entry.");
+
+ // Get bytes from stream without BOM.
+ bytes = PdfEncoders.RawEncoding.GetBytes(rawString);
+ result = Encoding.UTF32.GetString(bytes);
+ // Insert a readable encoding name.
+ result = result[..17] + MetadataEncodingType.UTF32LE + result[17..];
+ }
+ else if ((pos = rawString.IndexOf(utf32HeaderBE, StringComparison.Ordinal)) != -1)
+ {
+ PdfSharpLogHost.Logger.LogWarning(
+ "XMP metadata is encoded using UTF-32 big endian, which is not allowed in PDF files, but accepted by PDFsharp.");
+
+ if (pos > 0)
+ rawString = rawString[pos..];
+
+ const int leftPartLength = 4 * 17 + 3;
+ const int rightPartStart = leftPartLength + 4;
+ char leftQuot = rawString[leftPartLength - 4];
+ char rightQuot = rawString[rightPartStart];
+ if (leftQuot is '\"' or '\'' &&
+ rawString[(leftPartLength - 3)..(rightPartStart - 3)] == utf32BomBE &&
+ rightQuot is '\"' or '\'')
+ {
+ // Remove UTF-32BE BOM.
+ rawString = rawString[..(leftPartLength - 3)] + rawString[(rightPartStart - 3)..];
+ }
+ else
+ throw new InvalidOperationException(
+ "An UTF-32 big-endian encoded metadata stream has an invalid BOM entry.");
+
+ // Get bytes from stream without BOM.
+ bytes = PdfEncoders.RawEncoding.GetBytes(rawString);
+ result = Utf32BigEndianEncoding.GetString(bytes);
+ // Insert a readable encoding name.
+ result = result[..17] + MetadataEncodingType.UTF32BE + result[17..];
+ }
+ else
+ {
+ // We cannot determine the encoding and leave it as is.
+ result = rawString;
+ }
+
+ return result;
+ }
+
+ public static byte[] GetBytes(string xml)
+ {
+ if (!xml.StartsWith(header))
+ throw new InvalidOperationException("An XMP metadata string must start with '" + header + "'.");
+
+ const int leftPartLength = 17;
+ var rightPartStart = xml.IndexOf("\"", leftPartLength, StringComparison.Ordinal);
+ if (rightPartStart == -1)
+ rightPartStart = xml.IndexOf("'", leftPartLength + 1, StringComparison.Ordinal);
+ if (xml[leftPartLength - 1] is not ('\"' or '\'') || xml[rightPartStart] is not ('\"' or '\'') || rightPartStart < leftPartLength)
+ throw new InvalidOperationException("An XMP metadata string has an invalid header.");
+
+ // Get BOM tag and remove it.
+ var bomTag = xml[leftPartLength..rightPartStart];
+ xml = xml[..leftPartLength] + xml[rightPartStart..];
+ MetadataEncodingType encoding = MetadataEncodingType.UTF8;
+ if (bomTag.Length > 0)
+ {
+ if (!Enum.TryParse(bomTag, false, out encoding))
+ throw new InvalidOperationException($"Unknown metadata encoding tag '{bomTag}'.");
+ }
+
+ if (encoding != MetadataEncodingType.UTF8)
+ {
+ PdfSharpLogHost.Logger.LogError("XMP metadata must not be encoded using {Encoding} in a PDF file.", encoding);
+ }
+ byte[] bytes;
+ switch (encoding)
+ {
+ case MetadataEncodingType.UTF8:
+ bytes = Encoding.UTF8.GetBytes(xml);
+ xml = PdfEncoders.RawEncoding.GetString(bytes);
+ xml = xml[..leftPartLength] + utf8Bom + xml[leftPartLength..];
+ break;
+
+ case MetadataEncodingType.UTF16LE:
+ bytes = Encoding.Unicode.GetBytes(xml);
+ xml = PdfEncoders.RawEncoding.GetString(bytes);
+ var pos = 2 * 17;
+ xml = xml[..pos] + utf16BomLE + xml[pos..];
+ break;
+
+ case MetadataEncodingType.UTF16BE:
+ bytes = Encoding.BigEndianUnicode.GetBytes(xml);
+ xml = PdfEncoders.RawEncoding.GetString(bytes);
+ xml = xml[..(2 * 17)] + utf16BomBE + xml[(2 * 17)..];
+ break;
+
+ case MetadataEncodingType.UTF32LE:
+ bytes = Encoding.UTF32.GetBytes(xml);
+ xml = PdfEncoders.RawEncoding.GetString(bytes);
+ xml = xml[..(4 * 17)] + utf32BomLE + xml[(4 * 17)..];
+ break;
+
+ case MetadataEncodingType.UTF32BE:
+ bytes = Utf32BigEndianEncoding.GetBytes(xml);
+ xml = PdfEncoders.RawEncoding.GetString(bytes);
+ xml = xml[..(4 * 17)] + utf32BomBE + xml[(4 * 17)..];
+ break;
+
+ default:
+ throw new InvalidOperationException($"Unknown metadata encoding tag '{bomTag}'.");
+ }
+ bytes = PdfEncoders.RawEncoding.GetBytes(xml);
+ return bytes;
+ }
+
+ // Is only instantiated in some unit tests.
+ static UTF32Encoding Utf32BigEndianEncoding => _utf32BigEndianEncoding ??= new(true, false);
+ static UTF32Encoding? _utf32BigEndianEncoding;
+ }
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs
index 2a67643b..2d749616 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs
@@ -1,23 +1,34 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
-using PdfSharp.Internal;
using PdfSharp.Pdf.IO;
+// v7.0.0 Ready
+
namespace PdfSharp.Pdf
{
///
- /// Represents a PDF name value.
+ /// Represents a direct PDF name object.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
- public sealed class PdfName : PdfItem
+ public sealed class PdfName : PdfPrimitive
{
+ // Reference 2.0: 7.3.5 Name objects / Page 27
+
///
/// Initializes a new instance of the class.
///
public PdfName()
{
- Value = "/"; // Empty name.
+ Name = Name.Empty;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfName(Name name)
+ {
+ Name = name;
}
///
@@ -26,12 +37,7 @@ public PdfName()
///
public PdfName(string value)
{
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- if (value.Length == 0 || value[0] != '/')
- throw new ArgumentException(PsMsgs.NameMustStartWithSlash);
-
- Value = value;
+ Name = Name.FromCanonicalName(value);
}
///
@@ -40,19 +46,24 @@ public PdfName(string value)
public override bool Equals(object? obj)
{
if (obj is PdfName pdfName)
- return Value.Equals(pdfName.Value);
+ return Name.Equals(pdfName.Name);
return Value.Equals(obj);
}
///
/// Returns the hash code for this instance.
///
- public override int GetHashCode() => Value.GetHashCode();
+ public override int GetHashCode() => Name.GetHashCode();
+
+ ///
+ /// Gets the name as a canonical name string.
+ ///
+ public string Value => Name.Value;
///
- /// Gets the name as a string.
+ /// Gets the underlying Name object.
///
- public string Value { get; }
+ public Name Name { get; }
///
/// Returns the name. The string always begins with a slash.
@@ -62,7 +73,7 @@ public override bool Equals(object? obj)
///
/// Determines whether the specified name and string are equal.
///
- public static bool operator ==(PdfName? name, string? str) // BUG_OLD TODO_OLD check all operator ==
+ public static bool operator ==(PdfName? name, string? str)
{
if (name is null)
return str is null;
@@ -79,32 +90,26 @@ public override bool Equals(object? obj)
///
/// Gets an empty name.
///
- public static PdfName Empty => new("/");
+ public static PdfName Empty => _empty ??= new();
+
+ static PdfName? _empty;
///
- /// Adds the slash to a string, that is needed at the beginning of a PDFName string.
+ /// Adds the slash that is needed at the beginning of a PDFName.
///
- public static string AddSlash(string value) // TODO_OLD PDFsharp6: Naming. StL: WithSlash?
- {
- if (value.Length == 0)
- return "/";
-
- return value[0] != '/' ? $"/{value}" : value;
- }
+ [Obsolete("Use Name.MakeName")]
+ public static string AddSlash(string value)
+ => Name.MakeName(value);
///
- /// Removes the slash from a string, that is needed at the beginning of a PDFName string.
+ /// Removes the slash that is needed at the beginning of a PDFName.
///
- public static string RemoveSlash(string value) // TODO_OLD PDFsharp6: Naming. StL: WithoutSlash?
- {
- if (value.Length == 0 || value[0] != '/')
- return value;
-
- return value[1..];
- }
+ [Obsolete("Use Name.RemoveSlash")]
+ public static string RemoveSlash(string value)
+ => Name.RemoveSlash(value);
///
- /// Gets a PdfName form a string. The string must not start with a slash.
+ /// Gets a PdfName from a string. The string must not start with a slash.
///
public static PdfName FromString(string value)
{
@@ -120,26 +125,20 @@ public static PdfName FromString(string value)
///
/// Writes the name including the leading slash.
///
- internal override void WriteObject(PdfWriter writer)
- {
- // TODO_OLD: what if Unicode character are part of the name?
- // TODO_OLD: 7.3.5 Name objects: "In such situations, the sequence of bytes making up the name
- // object should be interpreted according to UTF-8, a variable-length byte-encoded
- // representation of Unicode in which the printable ASCII characters have the same
- // representations as in ASCII. This enables a name object to represent text virtually
- // in any natural language, subject to the implementation limit on the length of a name."
- writer.Write(this);
- }
+ internal override void WriteObject(PdfWriter writer)
+ => writer.Write(this);
///
/// Gets the comparer for this type.
///
- public static PdfXNameComparer Comparer => new PdfXNameComparer();
+ public static PdfNameComparer Comparer => _nameComparer ??= new();
+
+ static PdfNameComparer? _nameComparer;
///
/// Implements a comparer that compares PdfName objects.
///
- public class PdfXNameComparer : IComparer
+ public class PdfNameComparer : IComparer
{
///
/// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
@@ -152,10 +151,10 @@ public int Compare(PdfName? l, PdfName? r)
{
if (r != null)
return String.Compare(l.Value, r.Value, StringComparison.Ordinal);
- return -1;
+ return 1;
}
if (r != null)
- return 1;
+ return -1;
return 0;
}
}
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameObject.cs
index ba9c51c1..25827633 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameObject.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameObject.cs
@@ -1,24 +1,42 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using PdfSharp.Internal;
using PdfSharp.Pdf.IO;
namespace PdfSharp.Pdf
{
///
/// Represents an indirect name value. This type is not used by PDFsharp. If it is imported from
- /// an external PDF file, the value is converted into a direct object. Acrobat sometime uses indirect
+ /// an external PDF file, the value is converted into a direct object. Acrobat sometimes uses indirect
/// names to save space, because an indirect reference to a name may be shorter than a long name.
///
[DebuggerDisplay("({" + nameof(Value) + "})")]
- public sealed class PdfNameObject : PdfObject
+ public sealed class PdfNameObject : PdfPrimitiveObject
{
///
/// Initializes a new instance of the class.
///
public PdfNameObject()
{
- Value = "/"; // Empty name.
+ Name = Name.Empty;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PdfNameObject(Name name)
+ {
+ Name = name;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value.
+ public PdfNameObject(string value)
+ {
+ Name = value;
}
///
@@ -27,41 +45,54 @@ public PdfNameObject()
/// The document.
/// The value.
public PdfNameObject(PdfDocument document, string value)
- : base(document)
+ : base(document, true)
{
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- if (value.Length == 0 || value[0] != '/')
- throw new ArgumentException(PsMsgs.NameMustStartWithSlash);
+ Name = value;
+ }
- Value = value;
+ ///
+ /// Initializes a new instance of the class
+ /// without making it indirect.
+ /// Used in PDF parser only.
+ ///
+ /// The document.
+ /// The initial value.
+ /// If true creates an indirect object.
+ internal PdfNameObject(PdfDocument document, Name name, bool createIndirect)
+ : base(document, createIndirect)
+ {
+ Name = name;
}
///
/// Determines whether the specified object is equal to the current object.
///
- public override bool Equals(object? obj)
- => Value.Equals(obj);
+ public override bool Equals(object? obj)
+ {
+ if (obj is PdfName pdfName)
+ return Name.Equals(pdfName.Name);
+ return Value.Equals(obj);
+ }
///
/// Serves as a hash function for this type.
///
- public override int GetHashCode()
- => Value.GetHashCode();
+ public override int GetHashCode() => Name.GetHashCode();
+
+ ///
+ /// Gets the name as a canonical name string.
+ ///
+ public string Value => Name.Value;
///
- /// Gets or sets the name value.
+ /// Gets the underlying Name object.
///
- public string Value { get; set; }
+ public Name Name { get; }
///
/// Returns the name. The string always begins with a slash.
///
- public override string ToString()
- {
- // TODO_OLD: Encode characters.
- return Value;
- }
+ public override string ToString() => Value;
///
/// Determines whether a name is equal to a string.
diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameTreeNode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameTreeNode.cs
index 3b131c1d..0bf5e5fe 100644
--- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameTreeNode.cs
+++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameTreeNode.cs
@@ -1,18 +1,26 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.
+using Microsoft.Extensions.Logging;
+using PdfSharp.Logging;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.IO;
+using System;
+using System.Reflection;
+
+// ReSharper disable UnusedMember.Global // TODO
+#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member
namespace PdfSharp.Pdf
{
///
- /// Represents a name tree node.
+ /// Represents a node in a name tree.
///
[DebuggerDisplay("({" + nameof(DebuggerDisplay) + "})")]
- public sealed class PdfNameTreeNode : PdfDictionary
+ public class PdfNameTreeNode : PdfDictionary
{
- // Reference: 3.8.5 Name Trees / Page 161
+ // Reference 1.7: 3.8.5 Name Trees / Page 161
+ // Reference 2.0: 7.9.6 Name trees / Page 119
///
/// Initializes a new instance of the class.
@@ -21,9 +29,10 @@ public PdfNameTreeNode()
{ }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of this class using the elements of the specified dictionary.
+ /// After this type transformation the specified dictionary is dead and cannot be used anymore.
///
- public PdfNameTreeNode(PdfDictionary dict)
+ internal PdfNameTreeNode(PdfDictionary dict)
: base(dict)
{
Initialize();
@@ -37,9 +46,37 @@ public PdfNameTreeNode(PdfDictionary dict)
///
/// Gets a value indicating whether this instance is a root node.
///
- public bool IsRoot
+ public bool IsRoot => Parent == null;
+
+ public bool IsLeaf => Parent != null && Kids == null;
+
+ public bool IsIntermediate => Parent != null && Kids != null;
+
+ public PdfNameTreeKids? Kids
+ {
+ get
+ {
+ var kids = Elements.GetValue(Keys.Kids);
+ return kids;
+ }
+ }
+
+ public PdfNameTreeNames? Names
{
- get => Parent == null;
+ get
+ {
+ var names = Elements.GetValue(Keys.Names);
+ return names;
+ }
+ }
+
+ public PdfNameTreeLimits? Limits
+ {
+ get
+ {
+ var limits = Elements.GetValue