use std::collections::HashMap;
use std::fmt::Debug;
use pdf_writer::types::OutputIntentSubtype;
use xmp_writer::pdfa::PdfAExtSchemasWriter;
use xmp_writer::XmpWriter;
use crate::color::separation::SeparationColorant;
use crate::color::separation::SeparationSpace;
use crate::color::RegularColor;
use crate::configure::PdfVersion;
use crate::interchange::embed::EmbedError;
use crate::surface::Location;
use crate::text::Font;
use crate::text::GlyphId;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ValidationError {
TooLongString,
TooLongName,
TooLongArray,
TooLongDictionary,
TooLargeFloat,
TooManyIndirectObjects,
TooHighQNestingLevel,
ContainsPostScript(Option<Location>),
MissingCMYKProfile,
InconsistentSeparationFallback(SeparationColorant),
ContainsNotDefGlyph(Font, Option<Location>, String),
NoCodepointMapping(Font, GlyphId, Option<Location>),
InvalidCodepointMapping(Font, GlyphId, char, Option<Location>),
UnicodePrivateArea(Font, GlyphId, char, Option<Location>),
RestrictedLicense(Font),
NoDocumentLanguage,
NoDocumentTitle,
MissingAltText(Option<Location>),
MissingHeadingTitle,
MissingDocumentOutline,
MissingAnnotationAltText(Option<Location>),
MissingDocumentDate,
Transparency(Option<Location>),
ImageInterpolation(Option<Location>),
EmbeddedFile(EmbedError, Option<Location>),
MissingTagging,
EmbeddedPDF(Option<Location>),
RequiresNewerPdfVersion(VersionedFeature, Option<Location>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VersionedFeature {
StructureOrderTabbing,
HeaderFooterArtifactSubtypes,
TableHeaderScope,
}
impl VersionedFeature {
pub fn minimum_pdf_version(&self) -> PdfVersion {
match self {
VersionedFeature::StructureOrderTabbing => PdfVersion::Pdf15,
VersionedFeature::HeaderFooterArtifactSubtypes => PdfVersion::Pdf17,
VersionedFeature::TableHeaderScope => PdfVersion::Pdf15,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
pub struct Validators {
a: Option<Archival>,
ua: Option<Accessibility>,
}
impl Validators {
pub fn prohibits(self, error: &ValidationError) -> Option<Self> {
let a = self.a.filter(|v| v.prohibits(error));
let ua = self.ua.filter(|v| v.prohibits(error));
let any = a.is_some() || ua.is_some();
any.then_some(Self { a, ua })
}
pub fn is_empty(self) -> bool {
self.a.is_none() && self.ua.is_none()
}
pub fn len(self) -> usize {
(if self.a.is_some() { 1 } else { 0 }) + (if self.ua.is_some() { 1 } else { 0 })
}
pub fn archival(self) -> Option<Archival> {
self.a
}
pub fn accessibility(self) -> Option<Accessibility> {
self.ua
}
pub(crate) fn requires_codepoint_mappings(self) -> bool {
self.into_iter().any(Validator::requires_codepoint_mappings)
}
pub(crate) fn requires_display_doc_title(self) -> bool {
self.ua
.is_some_and(Accessibility::requires_display_doc_title)
}
pub(crate) fn requires_no_device_cs(self) -> bool {
self.a.is_some_and(Archival::requires_no_device_cs)
}
pub(crate) fn requires_annotation_flags(self) -> bool {
self.a.is_some_and(Archival::requires_annotation_flags)
}
pub(crate) fn requires_tagging(self) -> bool {
self.into_iter().any(Validator::requires_tagging)
}
pub(crate) fn requires_xmp_metadata(self) -> bool {
self.into_iter().any(Validator::requires_xmp_metadata)
}
pub(crate) fn requires_xmp_metadata_extension_schema(self) -> bool {
self.a
.is_some_and(Archival::requires_xmp_metadata_extension_schema)
}
pub(crate) fn prohibits_instance_id_in_xmp_metadata(self) -> bool {
self.a
.is_some_and(Archival::prohibits_instance_id_in_xmp_metadata)
}
pub(crate) fn requires_file_provenance_information(self) -> bool {
self.a
.is_some_and(Archival::requires_file_provenance_information)
}
pub(crate) fn prohibits_info_dict(self) -> bool {
self.a.is_some_and(Archival::prohibits_info_dict)
}
pub(crate) fn requires_binary_header(self) -> bool {
self.a.is_some_and(Archival::requires_binary_header)
}
pub(crate) fn requires_embedded_files_when_empty(self) -> bool {
self.a
.is_some_and(Archival::requires_embedded_files_when_empty)
}
pub(crate) fn specifies_associated_files(self) -> bool {
self.a.is_some_and(Archival::specifies_associated_files)
}
pub(crate) fn output_intent(self) -> Option<OutputIntentSubtype<'static>> {
self.a.map(Archival::output_intent)
}
pub(crate) fn write_xmp(self, xmp: &mut XmpWriter) {
if self.requires_xmp_metadata_extension_schema() {
let mut extension_schemas = xmp.extension_schemas();
if let Some(a) = self.a {
a.write_xmp_extension_schema_description(&mut extension_schemas);
}
if let Some(ua) = self.ua {
ua.write_xmp_extension_schema_description(&mut extension_schemas);
}
}
if let Some(a) = self.a {
a.write_xmp(xmp);
}
if let Some(ua) = self.ua {
ua.write_xmp(xmp);
}
}
pub fn max(self) -> PdfVersion {
self.a
.map_or(PdfVersion::MAX, |v| v.max())
.min(self.ua.map_or(PdfVersion::MAX, |v| v.max()))
}
pub fn min(self) -> Option<PdfVersion> {
self.a
.and_then(|v| v.min())
.max(self.ua.and_then(|v| v.min()))
}
}
impl IntoIterator for Validators {
type Item = Validator;
type IntoIter = std::iter::Flatten<std::array::IntoIter<Option<Validator>, 2>>;
fn into_iter(self) -> Self::IntoIter {
[self.a.map(Validator::A), self.ua.map(Validator::Ua)]
.into_iter()
.flatten()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
pub struct ValidatorsBuilder(Validators);
impl ValidatorsBuilder {
pub fn set_validator(self, validator: Validator) -> Self {
match validator {
Validator::A(a) => self.with_archival_validator(a),
Validator::Ua(ua) => self.with_accessibility_validator(ua),
}
}
pub fn with_archival_validator(mut self, archival: Archival) -> Self {
self.0.a = Some(archival);
self
}
pub fn with_accessibility_validator(mut self, accessibility: Accessibility) -> Self {
self.0.ua = Some(accessibility);
self
}
pub(crate) fn finish(self) -> Result<Validators, Validators> {
let min = self.0.min().unwrap_or(PdfVersion::MIN);
let max = self.0.max();
if min > max {
Err(self.0)
} else {
Ok(self.0)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Validator {
A(Archival),
Ua(Accessibility),
}
impl Validator {
fn requires_codepoint_mappings(self) -> bool {
match self {
Self::A(a) => a.requires_codepoint_mappings(),
Self::Ua(ua) => ua.requires_codepoint_mappings(),
}
}
fn requires_tagging(self) -> bool {
match self {
Self::A(a) => a.requires_tagging(),
Self::Ua(ua) => ua.requires_tagging(),
}
}
fn requires_xmp_metadata(self) -> bool {
match self {
Self::A(a) => a.requires_xmp_metadata(),
Self::Ua(ua) => ua.requires_xmp_metadata(),
}
}
pub fn min(self) -> Option<PdfVersion> {
match self {
Self::A(a) => a.min(),
Self::Ua(ua) => ua.min(),
}
}
pub fn max(self) -> PdfVersion {
match self {
Self::A(a) => a.max(),
Self::Ua(ua) => ua.max(),
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::A(a) => a.as_str(),
Self::Ua(ua) => ua.as_str(),
}
}
}
impl From<Archival> for Validator {
fn from(a: Archival) -> Self {
Self::A(a)
}
}
impl From<Accessibility> for Validator {
fn from(ua: Accessibility) -> Self {
Self::Ua(ua)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
pub enum Archival {
A1_A,
A1_B,
A2_A,
A2_B,
A2_U,
A3_A,
A3_B,
A3_U,
A4,
A4F,
A4E,
}
impl Archival {
fn prohibits(self, error: &ValidationError) -> bool {
match (self, error) {
(
Self::A1_A | Self::A1_B,
ValidationError::TooLongString
| ValidationError::TooLongName
| ValidationError::TooLongArray
| ValidationError::TooLongDictionary
| ValidationError::TooLargeFloat
| ValidationError::TooManyIndirectObjects
| ValidationError::TooHighQNestingLevel
| ValidationError::ContainsPostScript(_)
| ValidationError::MissingCMYKProfile
| ValidationError::RestrictedLicense(_)
| ValidationError::MissingDocumentDate
| ValidationError::Transparency(_)
| ValidationError::ImageInterpolation(_)
| ValidationError::EmbeddedFile(EmbedError::Existence, _)
| ValidationError::EmbeddedPDF(_),
) => true,
(
Self::A1_A | Self::A1_B,
ValidationError::InconsistentSeparationFallback(_)
| ValidationError::InvalidCodepointMapping(_, _, _, _)
| ValidationError::UnicodePrivateArea(_, _, _, _)
| ValidationError::NoDocumentTitle
| ValidationError::MissingHeadingTitle
| ValidationError::MissingDocumentOutline
| ValidationError::EmbeddedFile(_, _)
| ValidationError::RequiresNewerPdfVersion(
VersionedFeature::HeaderFooterArtifactSubtypes
| VersionedFeature::StructureOrderTabbing
| VersionedFeature::TableHeaderScope,
_,
),
) => false,
(
Self::A1_A | Self::A1_B,
ValidationError::ContainsNotDefGlyph(_, _, _)
| ValidationError::NoCodepointMapping(_, _, _)
| ValidationError::NoDocumentLanguage
| ValidationError::MissingAltText(_)
| ValidationError::MissingAnnotationAltText(_)
| ValidationError::MissingTagging,
) => self == Self::A1_A,
(
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
ValidationError::TooLongString
| ValidationError::TooLongName
| ValidationError::TooManyIndirectObjects
| ValidationError::TooHighQNestingLevel
| ValidationError::ContainsPostScript(_)
| ValidationError::MissingCMYKProfile
| ValidationError::InconsistentSeparationFallback(_)
| ValidationError::ContainsNotDefGlyph(_, _, _)
| ValidationError::RestrictedLicense(_)
| ValidationError::MissingDocumentDate
| ValidationError::ImageInterpolation(_)
| ValidationError::EmbeddedPDF(_),
) => true,
(
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
ValidationError::TooLongArray
| ValidationError::TooLongDictionary
| ValidationError::TooLargeFloat
| ValidationError::NoDocumentTitle
| ValidationError::Transparency(_)
| ValidationError::MissingHeadingTitle
| ValidationError::MissingDocumentOutline
| ValidationError::RequiresNewerPdfVersion(
VersionedFeature::HeaderFooterArtifactSubtypes
| VersionedFeature::StructureOrderTabbing
| VersionedFeature::TableHeaderScope,
_,
),
) => false,
(
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
ValidationError::EmbeddedFile(EmbedError::Existence, _),
) => self == Self::A2_A || self == Self::A2_B || self == Self::A2_U,
(
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
ValidationError::EmbeddedFile(
EmbedError::MissingDate
| EmbedError::MissingDescription
| EmbedError::MissingMimeType,
_,
),
) => self == Self::A3_A || self == Self::A3_B || self == Self::A3_U,
(
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
ValidationError::UnicodePrivateArea(_, _, _, _)
| ValidationError::NoDocumentLanguage
| ValidationError::MissingAltText(_)
| ValidationError::MissingAnnotationAltText(_)
| ValidationError::MissingTagging,
) => self == Self::A2_A || self == Self::A3_A,
(
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
ValidationError::NoCodepointMapping(_, _, _)
| ValidationError::InvalidCodepointMapping(_, _, _, _),
) => {
self == Self::A2_A || self == Self::A2_U || self == Self::A3_A || self == Self::A3_U
}
(
Self::A4 | Self::A4F | Self::A4E,
ValidationError::MissingCMYKProfile
| ValidationError::InconsistentSeparationFallback(_)
| ValidationError::ContainsNotDefGlyph(_, _, _)
| ValidationError::NoCodepointMapping(_, _, _)
| ValidationError::InvalidCodepointMapping(_, _, _, _)
| ValidationError::UnicodePrivateArea(_, _, _, _)
| ValidationError::RestrictedLicense(_)
| ValidationError::MissingDocumentDate
| ValidationError::ImageInterpolation(_)
| ValidationError::EmbeddedPDF(_),
) => true,
(
Self::A4 | Self::A4F | Self::A4E,
ValidationError::TooLongString
| ValidationError::TooLongName
| ValidationError::TooLongArray
| ValidationError::TooLongDictionary
| ValidationError::TooLargeFloat
| ValidationError::TooManyIndirectObjects
| ValidationError::TooHighQNestingLevel
| ValidationError::ContainsPostScript(_)
| ValidationError::NoDocumentLanguage
| ValidationError::NoDocumentTitle
| ValidationError::MissingAltText(_)
| ValidationError::MissingHeadingTitle
| ValidationError::MissingDocumentOutline
| ValidationError::MissingAnnotationAltText(_)
| ValidationError::Transparency(_)
| ValidationError::EmbeddedFile(
EmbedError::MissingDate | EmbedError::MissingMimeType,
_,
)
| ValidationError::MissingTagging
| ValidationError::RequiresNewerPdfVersion(
VersionedFeature::HeaderFooterArtifactSubtypes
| VersionedFeature::StructureOrderTabbing
| VersionedFeature::TableHeaderScope,
_,
),
) => false,
(
Self::A4 | Self::A4F | Self::A4E,
ValidationError::EmbeddedFile(EmbedError::Existence, _),
) => self == Self::A4,
(
Self::A4 | Self::A4F | Self::A4E,
ValidationError::EmbeddedFile(EmbedError::MissingDescription, _),
) => self == Self::A4,
}
}
fn requires_codepoint_mappings(self) -> bool {
match self {
Self::A1_A
| Self::A2_A
| Self::A2_U
| Self::A3_A
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => true,
Self::A1_B | Self::A2_B | Self::A3_B => false,
}
}
fn requires_no_device_cs(self) -> bool {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => true,
}
}
fn requires_annotation_flags(self) -> bool {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => true,
}
}
fn requires_tagging(self) -> bool {
match self {
Self::A1_A | Self::A2_A | Self::A3_A => true,
Self::A1_B
| Self::A2_B
| Self::A2_U
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => false,
}
}
fn requires_xmp_metadata(self) -> bool {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => true,
}
}
fn requires_xmp_metadata_extension_schema(self) -> bool {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U => true,
Self::A4 | Self::A4F | Self::A4E => false,
}
}
fn prohibits_instance_id_in_xmp_metadata(self) -> bool {
match self {
Self::A1_A | Self::A1_B => true,
Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => false,
}
}
fn requires_file_provenance_information(self) -> bool {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => true,
}
}
fn prohibits_info_dict(self) -> bool {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U => false,
Self::A4 | Self::A4F | Self::A4E => true,
}
}
fn requires_binary_header(self) -> bool {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => true,
}
}
fn requires_embedded_files_when_empty(self) -> bool {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4E => false,
Self::A4F => true,
}
}
fn specifies_associated_files(self) -> bool {
match self {
Self::A3_A | Self::A3_B | Self::A3_U => true,
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A4
| Self::A4F
| Self::A4E => false,
}
}
fn output_intent(self) -> OutputIntentSubtype<'static> {
match self {
Self::A1_A
| Self::A1_B
| Self::A2_A
| Self::A2_B
| Self::A2_U
| Self::A3_A
| Self::A3_B
| Self::A3_U
| Self::A4
| Self::A4F
| Self::A4E => OutputIntentSubtype::PDFA,
}
}
fn write_xmp(self, xmp: &mut XmpWriter) {
match self {
Self::A1_A => {
xmp.pdfa_part(1);
xmp.pdfa_conformance("A");
}
Self::A1_B => {
xmp.pdfa_part(1);
xmp.pdfa_conformance("B");
}
Self::A2_A => {
xmp.pdfa_part(2);
xmp.pdfa_conformance("A");
}
Self::A2_B => {
xmp.pdfa_part(2);
xmp.pdfa_conformance("B");
}
Self::A2_U => {
xmp.pdfa_part(2);
xmp.pdfa_conformance("U");
}
Self::A3_A => {
xmp.pdfa_part(3);
xmp.pdfa_conformance("A");
}
Self::A3_B => {
xmp.pdfa_part(3);
xmp.pdfa_conformance("B");
}
Self::A3_U => {
xmp.pdfa_part(3);
xmp.pdfa_conformance("U");
}
Self::A4 => {
xmp.pdfa_part(4);
xmp.pdfa_rev(2020);
}
Self::A4F => {
xmp.pdfa_part(4);
xmp.pdfa_rev(2020);
xmp.pdfa_conformance("F");
}
Self::A4E => {
xmp.pdfa_part(4);
xmp.pdfa_rev(2020);
xmp.pdfa_conformance("E");
}
}
}
fn write_xmp_extension_schema_description(
self,
extension_schemas: &mut PdfAExtSchemasWriter<'_, '_>,
) {
if !self.requires_xmp_metadata_extension_schema() {
return;
}
extension_schemas
.xmp_media_management()
.properties()
.describe_instance_id();
extension_schemas.pdf().properties().describe_all();
}
pub fn as_str(self) -> &'static str {
match self {
Self::A1_A => "PDF/A-1a",
Self::A1_B => "PDF/A-1b",
Self::A2_A => "PDF/A-2a",
Self::A2_B => "PDF/A-2b",
Self::A2_U => "PDF/A-2u",
Self::A3_A => "PDF/A-3a",
Self::A3_B => "PDF/A-3b",
Self::A3_U => "PDF/A-3u",
Self::A4 => "PDF/A-4",
Self::A4F => "PDF/A-4f",
Self::A4E => "PDF/A-4e",
}
}
pub const fn min(self) -> Option<PdfVersion> {
match self {
Self::A1_A | Self::A1_B => Some(PdfVersion::Pdf14),
Self::A2_A | Self::A2_B | Self::A2_U => Some(PdfVersion::Pdf14),
Self::A3_A | Self::A3_B | Self::A3_U => Some(PdfVersion::Pdf14),
Self::A4 | Self::A4F | Self::A4E => Some(PdfVersion::Pdf20),
}
}
pub const fn max(self) -> PdfVersion {
match self {
Self::A1_A | Self::A1_B => PdfVersion::Pdf14,
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U => {
PdfVersion::Pdf17
}
Self::A4 | Self::A4F | Self::A4E => PdfVersion::Pdf20,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
pub enum Accessibility {
UA1,
}
impl Accessibility {
fn prohibits(self, error: &ValidationError) -> bool {
match (self, error) {
(
Self::UA1,
ValidationError::ContainsNotDefGlyph(_, _, _)
| ValidationError::NoCodepointMapping(_, _, _)
| ValidationError::InvalidCodepointMapping(_, _, _, _)
| ValidationError::RestrictedLicense(_)
| ValidationError::NoDocumentTitle
| ValidationError::MissingAltText(_)
| ValidationError::MissingHeadingTitle
| ValidationError::MissingDocumentOutline
| ValidationError::MissingAnnotationAltText(_)
| ValidationError::EmbeddedFile(EmbedError::MissingDescription, _)
| ValidationError::MissingTagging
| ValidationError::EmbeddedPDF(_)
| ValidationError::RequiresNewerPdfVersion(
VersionedFeature::HeaderFooterArtifactSubtypes
| VersionedFeature::StructureOrderTabbing
| VersionedFeature::TableHeaderScope,
_,
),
) => true,
(
Self::UA1,
ValidationError::TooLongString
| ValidationError::TooLongName
| ValidationError::TooLongArray
| ValidationError::TooLongDictionary
| ValidationError::TooLargeFloat
| ValidationError::TooManyIndirectObjects
| ValidationError::TooHighQNestingLevel
| ValidationError::ContainsPostScript(_)
| ValidationError::MissingCMYKProfile
| ValidationError::InconsistentSeparationFallback(_)
| ValidationError::UnicodePrivateArea(_, _, _, _)
| ValidationError::NoDocumentLanguage
| ValidationError::Transparency(_)
| ValidationError::ImageInterpolation(_)
| ValidationError::EmbeddedFile(
EmbedError::Existence | EmbedError::MissingDate | EmbedError::MissingMimeType,
_,
)
| ValidationError::MissingDocumentDate,
) => false,
}
}
fn requires_codepoint_mappings(self) -> bool {
match self {
Self::UA1 => true,
}
}
fn requires_display_doc_title(self) -> bool {
match self {
Self::UA1 => true,
}
}
const fn requires_tagging(self) -> bool {
true
}
fn requires_xmp_metadata(self) -> bool {
match self {
Self::UA1 => true,
}
}
fn write_xmp(self, xmp: &mut XmpWriter) {
match self {
Self::UA1 => {
xmp.pdfua_part(1);
}
}
}
fn write_xmp_extension_schema_description(
self,
extension_schemas: &mut PdfAExtSchemasWriter<'_, '_>,
) {
extension_schemas.pdfua_id().properties().describe_part();
}
pub fn as_str(self) -> &'static str {
match self {
Self::UA1 => "PDF/UA-1",
}
}
pub const fn min(self) -> Option<PdfVersion> {
match self {
Self::UA1 => Some(PdfVersion::Pdf14),
}
}
pub const fn max(self) -> PdfVersion {
match self {
Self::UA1 => PdfVersion::Pdf17,
}
}
}
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct ValidationStore {
separation_fallback_map: HashMap<SeparationColorant, RegularColor>,
}
impl ValidationStore {
pub(crate) fn new() -> Self {
Default::default()
}
pub(crate) fn validate_separation(
&mut self,
separation: &SeparationSpace,
) -> Result<(), ValidationError> {
if self
.separation_fallback_map
.entry(separation.colorant.clone())
.or_insert(separation.fallback)
== &separation.fallback
{
Ok(())
} else {
Err(ValidationError::InconsistentSeparationFallback(
separation.colorant.clone(),
))
}
}
}