mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 04:22:20 +00:00
Merge #6232: M11: PDF/A verify checks that TIFF, EXIF and aux XMP properties have their value types (optimize.pdfa-validate)
# Conflicts: # crates/preflight/src/tests.rs # crates/preflight/src/xmp_types.rs # parity/acrobat-features.toml
This commit is contained in:
commit
dee86eecc1
3 files changed
+244
-8
No files matched your search
@@ -496,7 +496,8 @@ const XMP_NS: &str = concat!(
|
||||
r#"xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" "#,
|
||||
r#"xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:xmpDM="http://ns.adobe.com/xmp/1.0/DynamicMedia/" "#,
|
||||
r#"xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/" xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/" "#,
|
||||
r#"xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#" xmlns:pdf="http://ns.adobe.com/pdf/1.3/""#
|
||||
r#"xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#" xmlns:pdf="http://ns.adobe.com/pdf/1.3/" "#,
|
||||
r#"xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:exif="http://ns.adobe.com/exif/1.0/" xmlns:aux="http://ns.adobe.com/exif/1.0/aux/""#
|
||||
);
|
||||
|
||||
/// The mistyped predefined properties of a packet whose one description holds `body` (its
|
||||
@@ -720,6 +721,82 @@ fn xmp_value_types_are_reported_by_verify() {
|
||||
assert_eq!(found.iter().map(|i| i.object).collect::<Vec<_>>(), [Some(ObjRef::new(5, 0))]);
|
||||
}
|
||||
|
||||
/// PDF/A-2 6.6.2.3.1 for the TIFF, EXIF and Additional EXIF schemas of the September 2005 XMP
|
||||
/// Specification (pages 58–70), with the EXIF value types of pages 83–87.
|
||||
#[test]
|
||||
fn xmp_exif_schemas_have_their_value_types() {
|
||||
// TIFF: integers, closed choices of integer, rationals, sequences, language alternatives.
|
||||
assert!(element("tiff:ImageWidth", "256").is_empty());
|
||||
assert_eq!(element("tiff:ImageWidth", "256 mm"), ["tiff:ImageWidth"]);
|
||||
assert_eq!(element("tiff:Orientation", "4/1"), ["tiff:Orientation"]);
|
||||
assert!(element("tiff:XResolution", "72/1").is_empty());
|
||||
assert_eq!(element("tiff:XResolution", "<rdf:Seq><rdf:li>1/2</rdf:li></rdf:Seq>"), ["tiff:XResolution"]);
|
||||
assert!(element("tiff:BitsPerSample", "<rdf:Seq><rdf:li>8</rdf:li><rdf:li>8</rdf:li></rdf:Seq>").is_empty());
|
||||
assert_eq!(element("tiff:BitsPerSample", "<rdf:Seq><rdf:li>8</rdf:li><rdf:li>8.0</rdf:li></rdf:Seq>"), ["tiff:BitsPerSample"]);
|
||||
assert_eq!(element("tiff:TransferFunction", "<rdf:Bag><rdf:li>0</rdf:li></rdf:Bag>"), ["tiff:TransferFunction"]);
|
||||
assert_eq!(element("tiff:ImageDescription", "A picture"), ["tiff:ImageDescription"]);
|
||||
// EXIF: GPS coordinates (page 85).
|
||||
for good in ["51,30,26N", "0,5.25W", "120,5,0E", "8,0.000001S"] {
|
||||
assert!(element("exif:GPSLatitude", good).is_empty(), "{good}");
|
||||
}
|
||||
for bad in ["51.5N", "51,30,26", "51,30.N", "51,30,26X", "N", "51,30,2.6N", "a,30,26N", "51,30N", "51.5,30,26N"] {
|
||||
assert_eq!(element("exif:GPSLatitude", bad), ["exif:GPSLatitude"], "{bad}");
|
||||
}
|
||||
assert!(element("exif:GPSTimeStamp", "2016-02-01T13:19:21").is_empty());
|
||||
assert_eq!(element("exif:ISOSpeedRatings", "100"), ["exif:ISOSpeedRatings"]);
|
||||
// EXIF structures: Flash, OECF/SFR, CFAPattern, DeviceSettings (pages 85–87).
|
||||
assert!(mistyped(r#"><exif:Flash rdf:parseType="Resource"><exif:Fired>True</exif:Fired><exif:Mode>3</exif:Mode></exif:Flash>"#).is_empty());
|
||||
assert_eq!(mistyped(r#"><exif:Flash rdf:parseType="Resource"><exif:Fired>yes</exif:Fired></exif:Flash>"#), ["exif:Flash"]);
|
||||
assert_eq!(mistyped(r#"><exif:Flash exif:Return="2.0"/>"#), ["exif:Flash"]);
|
||||
assert_eq!(element("exif:Flash", "fired"), ["exif:Flash"]);
|
||||
assert!(
|
||||
mistyped(r#"><exif:OECF rdf:parseType="Resource"><exif:Values><rdf:Seq><rdf:li>1/2</rdf:li></rdf:Seq></exif:Values></exif:OECF>"#).is_empty()
|
||||
);
|
||||
assert_eq!(
|
||||
mistyped(r#"><exif:OECF rdf:parseType="Resource"><exif:Values><rdf:Seq><rdf:li>0.5</rdf:li></rdf:Seq></exif:Values></exif:OECF>"#),
|
||||
["exif:OECF"]
|
||||
);
|
||||
assert_eq!(
|
||||
mistyped(
|
||||
r#"><exif:CFAPattern rdf:parseType="Resource"><exif:Values><rdf:Seq><rdf:li>1/2</rdf:li></rdf:Seq></exif:Values></exif:CFAPattern>"#
|
||||
),
|
||||
["exif:CFAPattern"]
|
||||
);
|
||||
assert_eq!(
|
||||
mistyped(r#"><exif:DeviceSettingDescription rdf:parseType="Resource"><exif:Columns>2.0</exif:Columns></exif:DeviceSettingDescription>"#),
|
||||
["exif:DeviceSettingDescription"]
|
||||
);
|
||||
assert_eq!(
|
||||
mistyped(r#"><exif:DeviceSettingDescription rdf:parseType="Resource"><exif:Settings>a</exif:Settings></exif:DeviceSettingDescription>"#),
|
||||
["exif:DeviceSettingDescription"]
|
||||
);
|
||||
// Additional EXIF properties (page 69).
|
||||
assert!(element("aux:Lens", "50mm").is_empty());
|
||||
assert_eq!(element("aux:Lens", "<rdf:Seq><rdf:li>50mm</rdf:li></rdf:Seq>"), ["aux:Lens"]);
|
||||
assert_eq!(element("aux:SerialNumber", "<rdf:Bag><rdf:li>1</rdf:li></rdf:Bag>"), ["aux:SerialNumber"]);
|
||||
}
|
||||
|
||||
/// PDF/A-2 6.6.2.3.1 for the TIFF, EXIF and Additional EXIF schemas: a property is known by its
|
||||
/// namespace URI, whatever prefix the packet binds to it, and is named by the namespace's
|
||||
/// preferred prefix.
|
||||
#[test]
|
||||
fn xmp_exif_properties_are_known_by_namespace_uri() {
|
||||
let tiff = r#"xmlns:foo="http://ns.adobe.com/tiff/1.0/""#;
|
||||
assert_eq!(mistyped(&format!(r#"><foo:ImageWidth {tiff}>256 mm</foo:ImageWidth>"#)), ["tiff:ImageWidth"]);
|
||||
assert!(mistyped(&format!(r#"><foo:ImageWidth {tiff}>256</foo:ImageWidth>"#)).is_empty());
|
||||
assert_eq!(mistyped(r#"xmlns:bar="http://ns.adobe.com/exif/1.0/" bar:GPSLatitude="51.5N">"#), ["exif:GPSLatitude"]);
|
||||
assert_eq!(
|
||||
mistyped(r#"><baz:Lens xmlns:baz="http://ns.adobe.com/exif/1.0/aux/"><rdf:Seq><rdf:li>50mm</rdf:li></rdf:Seq></baz:Lens>"#),
|
||||
["aux:Lens"]
|
||||
);
|
||||
// The same local names in another namespace, even under the preferred prefixes, are not these
|
||||
// properties.
|
||||
let other = r#"xmlns:tiff="http://example.com/not-tiff/""#;
|
||||
assert!(mistyped(&format!(r#"><tiff:ImageWidth {other}>256 mm</tiff:ImageWidth>"#)).is_empty());
|
||||
assert!(mistyped(r#"xmlns:exif="http://example.com/not-exif/" exif:GPSLatitude="51.5N">"#).is_empty());
|
||||
assert!(mistyped(r#"><aux:Lens xmlns:aux="http://example.com/not-aux/"><rdf:Seq><rdf:li>50mm</rdf:li></rdf:Seq></aux:Lens>"#).is_empty());
|
||||
}
|
||||
|
||||
/// Holes are filled in one pass: the first `{}` from `value`, named holes from `values`, and what
|
||||
/// has nothing for it, a value's own braces and a brace left open stay as they are.
|
||||
#[test]
|
||||
@@ -755,6 +832,12 @@ fn xmp_mistyped_properties_name_their_type() {
|
||||
[t("xmpMM:History", "seq ResourceEvent"), t("xmpTPg:MaxPageSize", "Dimensions")]
|
||||
);
|
||||
assert_eq!(types("><xmpMM:Pantry>p</xmpMM:Pantry>"), [t("xmpMM:Pantry", "bag struct")]);
|
||||
// The TIFF and EXIF types, as the 2005 edition writes them.
|
||||
assert_eq!(types(r#"tiff:XResolution="0.5" exif:GPSLatitude="N">"#), [t("exif:GPSLatitude", "GPSCoordinate"), t("tiff:XResolution", "Rational")]);
|
||||
assert_eq!(
|
||||
types("><tiff:BitsPerSample>8</tiff:BitsPerSample><exif:Flash>fired</exif:Flash><exif:OECF>o</exif:OECF>"),
|
||||
[t("exif:Flash", "Flash"), t("exif:OECF", "OECF/SFR"), t("tiff:BitsPerSample", "seq Integer")]
|
||||
);
|
||||
}
|
||||
|
||||
/// PDF/A-2 6.6.2.3.1: a property is known by its namespace URI, whatever prefix the packet binds
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
//! The types come from ISO 16684-1:2011 (XMP Part 1) clause 8 for the Dublin Core, XMP Basic and
|
||||
//! XMP Rights Management namespaces, and from XMP Specification Part 2 (Additional Properties) for
|
||||
//! the Media Management, Basic Job Ticket, Paged-Text, Dynamic Media, Adobe PDF, Photoshop and
|
||||
//! Camera Raw namespaces, with the structures they use. A value is read as RDF/XML serializes it
|
||||
//! Camera Raw namespaces, with the structures they use. The TIFF, EXIF and Additional EXIF (`aux`)
|
||||
//! namespaces, which the current Part 2 leaves to CIPA DC-010, come from the September 2005 XMP
|
||||
//! Specification that PDF/A-2 cites: "EXIF Schemas" (pages 58–70) and "EXIF Schema Value Types"
|
||||
//! (pages 83–87). A value is read as RDF/XML serializes it
|
||||
//! (ISO 16684-1 7.9): a simple value as text or an attribute, an array as `rdf:Seq`, `rdf:Bag` or
|
||||
//! `rdf:Alt`, a structure as `rdf:parseType="Resource"`, an `rdf:Description` or attributes, and a
|
||||
//! qualified value through its `rdf:value` (element or attribute). Every metadata stream of the
|
||||
@@ -12,7 +15,8 @@
|
||||
//!
|
||||
//! The simple types are checked by their lexical form (ISO 16684-1 8.2.1): Boolean is `True` or
|
||||
//! `False`; Integer an optionally signed run of digits; Real a decimal; Date one of the six forms
|
||||
//! of 8.2.1.2; Rational two integers around a `/`, as Part 2's `xmpDM:scale` example writes it.
|
||||
//! of 8.2.1.2; Rational two integers around a `/`, as Part 2's `xmpDM:scale` example writes it and
|
||||
//! the 2005 edition defines it (page 84); GPSCoordinate `DDD,MM,SSk` or `DDD,MM.mmk` (page 85).
|
||||
//! The other simple types are text. A field a structure doesn't define, and a property a
|
||||
//! namespace doesn't define, are left to the clauses about them.
|
||||
|
||||
@@ -43,6 +47,8 @@ enum Ty {
|
||||
Boolean,
|
||||
Date,
|
||||
Rational,
|
||||
/// `DDD,MM,SSk` or `DDD,MM.mmk`, `k` one of N, S, E, W (2005 edition, page 85).
|
||||
GpsCoordinate,
|
||||
/// An alternative array of text items, each with an `xml:lang` (ISO 16684-1 8.2.2.4).
|
||||
LangAlt,
|
||||
Seq(&'static Ty),
|
||||
@@ -203,6 +209,19 @@ const ANCESTOR: &[Field] = &[(PHOTOSHOP, "AncestorID", Text)];
|
||||
/// Part 2 Table 32.
|
||||
const LAYER: &[Field] = &[(PHOTOSHOP, "LayerName", Text), (PHOTOSHOP, "LayerText", Text)];
|
||||
|
||||
const EXIF: &str = "http://ns.adobe.com/exif/1.0/";
|
||||
|
||||
/// Flash (2005 edition, page 85).
|
||||
const FLASH: &[Field] =
|
||||
&[(EXIF, "Fired", Boolean), (EXIF, "Return", Integer), (EXIF, "Mode", Integer), (EXIF, "Function", Boolean), (EXIF, "RedEyeMode", Boolean)];
|
||||
/// OECF/SFR (2005 edition, page 86).
|
||||
const OECF_SFR: &[Field] =
|
||||
&[(EXIF, "Columns", Integer), (EXIF, "Rows", Integer), (EXIF, "Names", Ty::Seq(&Text)), (EXIF, "Values", Ty::Seq(&Rational))];
|
||||
/// CFAPattern (2005 edition, page 87).
|
||||
const CFA_PATTERN: &[Field] = &[(EXIF, "Columns", Integer), (EXIF, "Rows", Integer), (EXIF, "Values", Ty::Seq(&Integer))];
|
||||
/// DeviceSettings (2005 edition, page 87).
|
||||
const DEVICE_SETTINGS: &[Field] = &[(EXIF, "Columns", Integer), (EXIF, "Rows", Integer), (EXIF, "Settings", Ty::Seq(&Text))];
|
||||
|
||||
const SCHEMAS: &[Schema] = &[
|
||||
// ISO 16684-1 Table 4.
|
||||
Schema {
|
||||
@@ -452,6 +471,126 @@ const SCHEMAS: &[Schema] = &[
|
||||
("WhiteBalance", Text),
|
||||
],
|
||||
},
|
||||
// 2005 edition, "EXIF Schema for TIFF Properties", pages 58–60. A Closed Choice of Integer is an
|
||||
// Integer.
|
||||
Schema {
|
||||
uri: "http://ns.adobe.com/tiff/1.0/",
|
||||
prefix: "tiff",
|
||||
properties: &[
|
||||
("ImageWidth", Integer),
|
||||
("ImageLength", Integer),
|
||||
("BitsPerSample", Ty::Seq(&Integer)),
|
||||
("Compression", Integer),
|
||||
("PhotometricInterpretation", Integer),
|
||||
("Orientation", Integer),
|
||||
("SamplesPerPixel", Integer),
|
||||
("PlanarConfiguration", Integer),
|
||||
("YCbCrSubSampling", Ty::Seq(&Integer)),
|
||||
("YCbCrPositioning", Integer),
|
||||
("XResolution", Rational),
|
||||
("YResolution", Rational),
|
||||
("ResolutionUnit", Integer),
|
||||
("TransferFunction", Ty::Seq(&Integer)),
|
||||
("WhitePoint", Ty::Seq(&Rational)),
|
||||
("PrimaryChromaticities", Ty::Seq(&Rational)),
|
||||
("YCbCrCoefficients", Ty::Seq(&Rational)),
|
||||
("ReferenceBlackWhite", Ty::Seq(&Rational)),
|
||||
("DateTime", Date),
|
||||
("ImageDescription", LangAlt),
|
||||
("Make", Text),
|
||||
("Model", Text),
|
||||
("Software", Text),
|
||||
("Artist", Text),
|
||||
("Copyright", LangAlt),
|
||||
],
|
||||
},
|
||||
// 2005 edition, "EXIF Schema for EXIF-specific Properties", pages 60–69.
|
||||
Schema {
|
||||
uri: EXIF,
|
||||
prefix: "exif",
|
||||
properties: &[
|
||||
("ExifVersion", Text),
|
||||
("FlashpixVersion", Text),
|
||||
("ColorSpace", Integer),
|
||||
("ComponentsConfiguration", Ty::Seq(&Integer)),
|
||||
("CompressedBitsPerPixel", Rational),
|
||||
("PixelXDimension", Integer),
|
||||
("PixelYDimension", Integer),
|
||||
("UserComment", LangAlt),
|
||||
("RelatedSoundFile", Text),
|
||||
("DateTimeOriginal", Date),
|
||||
("DateTimeDigitized", Date),
|
||||
("ExposureTime", Rational),
|
||||
("FNumber", Rational),
|
||||
("ExposureProgram", Integer),
|
||||
("SpectralSensitivity", Text),
|
||||
("ISOSpeedRatings", Ty::Seq(&Integer)),
|
||||
("OECF", Ty::Struct("OECF/SFR", OECF_SFR)),
|
||||
("ShutterSpeedValue", Rational),
|
||||
("ApertureValue", Rational),
|
||||
("BrightnessValue", Rational),
|
||||
("ExposureBiasValue", Rational),
|
||||
("MaxApertureValue", Rational),
|
||||
("SubjectDistance", Rational),
|
||||
("MeteringMode", Integer),
|
||||
("LightSource", Integer),
|
||||
("Flash", Ty::Struct("Flash", FLASH)),
|
||||
("FocalLength", Rational),
|
||||
("SubjectArea", Ty::Seq(&Integer)),
|
||||
("FlashEnergy", Rational),
|
||||
("SpatialFrequencyResponse", Ty::Struct("OECF/SFR", OECF_SFR)),
|
||||
("FocalPlaneXResolution", Rational),
|
||||
("FocalPlaneYResolution", Rational),
|
||||
("FocalPlaneResolutionUnit", Integer),
|
||||
("SubjectLocation", Ty::Seq(&Integer)),
|
||||
("ExposureIndex", Rational),
|
||||
("SensingMethod", Integer),
|
||||
("FileSource", Integer),
|
||||
("SceneType", Integer),
|
||||
("CFAPattern", Ty::Struct("CFAPattern", CFA_PATTERN)),
|
||||
("CustomRendered", Integer),
|
||||
("ExposureMode", Integer),
|
||||
("WhiteBalance", Integer),
|
||||
("DigitalZoomRatio", Rational),
|
||||
("FocalLengthIn35mmFilm", Integer),
|
||||
("SceneCaptureType", Integer),
|
||||
("GainControl", Integer),
|
||||
("Contrast", Integer),
|
||||
("Saturation", Integer),
|
||||
("Sharpness", Integer),
|
||||
("DeviceSettingDescription", Ty::Struct("DeviceSettings", DEVICE_SETTINGS)),
|
||||
("SubjectDistanceRange", Integer),
|
||||
("ImageUniqueID", Text),
|
||||
("GPSVersionID", Text),
|
||||
("GPSLatitude", Ty::GpsCoordinate),
|
||||
("GPSLongitude", Ty::GpsCoordinate),
|
||||
("GPSAltitudeRef", Integer),
|
||||
("GPSAltitude", Rational),
|
||||
("GPSTimeStamp", Date),
|
||||
("GPSSatellites", Text),
|
||||
("GPSStatus", Text),
|
||||
("GPSMeasureMode", Text),
|
||||
("GPSDOP", Rational),
|
||||
("GPSSpeedRef", Text),
|
||||
("GPSSpeed", Rational),
|
||||
("GPSTrackRef", Text),
|
||||
("GPSTrack", Rational),
|
||||
("GPSImgDirectionRef", Text),
|
||||
("GPSImgDirection", Rational),
|
||||
("GPSMapDatum", Text),
|
||||
("GPSDestLatitude", Ty::GpsCoordinate),
|
||||
("GPSDestLongitude", Ty::GpsCoordinate),
|
||||
("GPSDestBearingRef", Text),
|
||||
("GPSDestBearing", Rational),
|
||||
("GPSDestDistanceRef", Text),
|
||||
("GPSDestDistance", Rational),
|
||||
("GPSProcessingMethod", Text),
|
||||
("GPSAreaInformation", Text),
|
||||
("GPSDifferential", Integer),
|
||||
],
|
||||
},
|
||||
// 2005 edition, "EXIF Schema for Additional EXIF Properties", page 69.
|
||||
Schema { uri: "http://ns.adobe.com/exif/1.0/aux/", prefix: "aux", properties: &[("Lens", Text), ("SerialNumber", Text)] },
|
||||
];
|
||||
|
||||
/// Calls `f` with every metadata stream among the indirect objects `objs` (ISO 32000-1 14.3.2) that
|
||||
@@ -535,8 +674,8 @@ fn written(qname: &str, local: &str) -> String {
|
||||
format!("{out}:{local}")
|
||||
}
|
||||
|
||||
/// `ty` as the XMP Specification writes value types: `Integer`, `Lang Alt`, `seq Rational`, a
|
||||
/// structure by its type's name, such as `Dimensions`.
|
||||
/// `ty` as the XMP Specification writes value types: `Integer`, `GPSCoordinate`, `Lang Alt`,
|
||||
/// `seq Rational`, a structure by its type's name, such as `Flash`.
|
||||
fn named(ty: Ty) -> String {
|
||||
let array = |kind: &str, item: &Ty| format!("{kind} {}", named(*item));
|
||||
match ty {
|
||||
@@ -547,6 +686,7 @@ fn named(ty: Ty) -> String {
|
||||
Boolean => "Boolean".into(),
|
||||
Date => "Date".into(),
|
||||
Rational => "Rational".into(),
|
||||
Ty::GpsCoordinate => "GPSCoordinate".into(),
|
||||
LangAlt => "Lang Alt".into(),
|
||||
Ty::Seq(item) => array("seq", item),
|
||||
Ty::Bag(item) => array("bag", item),
|
||||
@@ -608,7 +748,7 @@ fn fits(ty: Ty, e: &Node<'_, '_>, depth: usize) -> bool {
|
||||
return blank(e) && blank(&n) && simple(ty, v) && predefined_ok(&n, deeper);
|
||||
}
|
||||
match ty {
|
||||
Ty::Text | Ty::Guid | Ty::Integer | Ty::Real | Ty::Boolean | Ty::Date | Ty::Rational => {
|
||||
Ty::Text | Ty::Guid | Ty::Integer | Ty::Real | Ty::Boolean | Ty::Date | Ty::Rational | Ty::GpsCoordinate => {
|
||||
// `rdf:resource` makes the value a URI, which only a text type takes.
|
||||
// The element is then empty.
|
||||
if e.attribute((RDF, "resource")).is_some() {
|
||||
@@ -716,12 +856,25 @@ fn simple(ty: Ty, v: &str) -> bool {
|
||||
Ty::Boolean => matches!(v, "True" | "False"),
|
||||
Ty::Date => date(v),
|
||||
Ty::Rational => v.split_once('/').is_some_and(|(n, d)| integer(n) && integer(d)),
|
||||
Ty::GpsCoordinate => gps_coordinate(v),
|
||||
Ty::Text | Ty::Guid => true,
|
||||
Ty::OneOf(types) => types.iter().any(|t| simple(*t, v)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// `DDD,MM,SSk` or `DDD,MM.mmk`: degrees, minutes and seconds or minutes with any number of
|
||||
/// fractional digits, then N, S, E or W (2005 edition, page 85).
|
||||
fn gps_coordinate(v: &str) -> bool {
|
||||
let Some(v) = v.strip_suffix(['N', 'S', 'E', 'W']) else { return false };
|
||||
let Some((degrees, rest)) = v.split_once(',') else { return false };
|
||||
let minutes_ok = match rest.split_once(',') {
|
||||
Some((minutes, seconds)) => digits(minutes) && digits(seconds),
|
||||
None => rest.split_once('.').is_some_and(|(m, f)| digits(m) && digits(f)),
|
||||
};
|
||||
digits(degrees) && minutes_ok
|
||||
}
|
||||
|
||||
fn digits(s: &str) -> bool {
|
||||
!s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
|
||||
}
|
||||
|
||||
@@ -8017,8 +8017,8 @@ milestone = "M11"
|
||||
status = "partial"
|
||||
commands = ["standards.pdfa"]
|
||||
tools = ["pdfa_verify"]
|
||||
evidence = ["crates/preflight/src/tests.rs::verify_reports_what_breaks_pdfa", "crates/preflight/src/tests.rs::standards_identification_uses_namespace_not_prefix", "crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/preflight/src/tests.rs::verify_reports_an_output_intent_that_does_not_match_device_colour", "crates/preflight/src/tests.rs::xmp_simple_values_have_their_lexical_forms", "crates/preflight/src/tests.rs::xmp_arrays_have_their_kind_and_item_types", "crates/preflight/src/tests.rs::xmp_structures_have_their_field_types", "crates/preflight/src/tests.rs::xmp_value_types_are_reported_by_verify", "crates/preflight/src/tests.rs::xmp_mistyped_properties_name_their_type", "crates/preflight/src/tests.rs::xmp_properties_are_known_by_namespace_uri_and_named_as_written", "crates/automation/tests/it/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/it/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||||
notes = "PDF/A-2b/3b structural rules: identification, the value types of the predefined XMP schemas (6.6.2.3.1: dc, xmp, xmpRights, xmpMM, xmpBJ, xmpTPg, xmpDM, pdf, photoshop, crs and their structures; the tiff, exif and aux namespaces, whose tables XMP Part 2 defers to CIPA DC-010, are not yet checked), encryption, output intent (present, and RGB/CMYK to match DeviceRGB/DeviceCMYK use), fonts embedded, annotations, actions, images, embedded files. Not yet: content-stream operator checks, transparency details, PDF/A-1 and -4, a veraPDF comparison."
|
||||
evidence = ["crates/preflight/src/tests.rs::verify_reports_what_breaks_pdfa", "crates/preflight/src/tests.rs::standards_identification_uses_namespace_not_prefix", "crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/preflight/src/tests.rs::verify_reports_an_output_intent_that_does_not_match_device_colour", "crates/preflight/src/tests.rs::xmp_simple_values_have_their_lexical_forms", "crates/preflight/src/tests.rs::xmp_arrays_have_their_kind_and_item_types", "crates/preflight/src/tests.rs::xmp_structures_have_their_field_types", "crates/preflight/src/tests.rs::xmp_value_types_are_reported_by_verify", "crates/preflight/src/tests.rs::xmp_mistyped_properties_name_their_type", "crates/preflight/src/tests.rs::xmp_properties_are_known_by_namespace_uri_and_named_as_written", "crates/preflight/src/tests.rs::xmp_exif_schemas_have_their_value_types", "crates/preflight/src/tests.rs::xmp_exif_properties_are_known_by_namespace_uri", "crates/automation/tests/it/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/it/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||||
notes = "PDF/A-2b/3b structural rules: identification, the value types of the predefined XMP schemas (6.6.2.3.1: dc, xmp, xmpRights, xmpMM, xmpBJ, xmpTPg, xmpDM, pdf, photoshop, crs, and tiff, exif and aux from the September 2005 XMP Specification, with their structures), encryption, output intent (present, and RGB/CMYK to match DeviceRGB/DeviceCMYK use), fonts embedded, annotations, actions, images, embedded files. Not yet: content-stream operator checks, transparency details, PDF/A-1 and -4, a veraPDF comparison."
|
||||
|
||||
[[feature]]
|
||||
id = "optimize.pdfa-convert"
|
||||
|
||||
Reference in new issue
Block a user