Skip to content

Commit 74da750

Browse files
committed
Implement into_pdf_1_7 and role_mapped_1_7 with an enum
1 parent abbfb54 commit 74da750

2 files changed

Lines changed: 151 additions & 125 deletions

File tree

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ pub mod types {
183183
pub use structure::{
184184
BlockLevelRoleSubtype, Direction, InlineLevelRoleSubtype,
185185
InlineLevelRoleSubtype2, NumberingStyle, OutlineItemFlags, PageLayout, PageMode,
186-
PhoneticAlphabet, StructRole, StructRole2, StructRoleType, StructRoleType2,
187-
TabOrder, TrappingStatus,
186+
PhoneticAlphabet, RoleMapOpts, StructRole, StructRole2, StructRole2Compat,
187+
StructRoleType, StructRoleType2, TabOrder, TrappingStatus,
188188
};
189189
pub use transitions::{TransitionAngle, TransitionStyle};
190190
pub use xobject::SMaskInData;

src/structure.rs

Lines changed: 149 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,22 +1116,21 @@ pub enum StructRole2 {
11161116
Strong,
11171117
/// A link.
11181118
Link,
1119-
/// An association between an annotation and the content it belongs to. PDF
1120-
/// 1.5+
1119+
/// An association between an annotation and the content it belongs to.
11211120
Annot,
11221121
/// Form widget.
11231122
Form,
1124-
/// Ruby annotation for CJK text. PDF 1.5+
1123+
/// Ruby annotation for CJK text.
11251124
Ruby,
1126-
/// Base text of a Ruby annotation. PDF 1.5+
1125+
/// Base text of a Ruby annotation.
11271126
RB,
1128-
/// Annotation text of a Ruby annotation. PDF 1.5+
1127+
/// Annotation text of a Ruby annotation.
11291128
RT,
1130-
/// Warichu annotation for CJK text. PDF 1.5+
1129+
/// Warichu annotation for CJK text.
11311130
Warichu,
1132-
/// Text of a Warichu annotation. PDF 1.5+
1131+
/// Text of a Warichu annotation.
11331132
WT,
1134-
/// Punctuation of a Warichu annotation. PDF 1.5+
1133+
/// Punctuation of a Warichu annotation.
11351134
WP,
11361135
/// A list.
11371136
L,
@@ -1214,125 +1213,88 @@ impl StructRole2 {
12141213
}
12151214
}
12161215

1217-
/// Return the corresponding PDF 1.7 [`StructRole`] for this role or `None`.
1218-
pub fn into_pdf_1_7(self) -> Option<StructRole> {
1219-
match self {
1220-
Self::Document => Some(StructRole::Document),
1221-
Self::DocumentFragment => None,
1222-
Self::Part => Some(StructRole::Part),
1223-
Self::Sect => Some(StructRole::Sect),
1224-
Self::Div => Some(StructRole::Div),
1225-
Self::Aside => None,
1226-
Self::NonStruct => Some(StructRole::NonStruct),
1227-
Self::P => Some(StructRole::P),
1228-
Self::Heading(n) if n.get() == 1 => Some(StructRole::H1),
1229-
Self::Heading(n) if n.get() == 2 => Some(StructRole::H2),
1230-
Self::Heading(n) if n.get() == 3 => Some(StructRole::H3),
1231-
Self::Heading(n) if n.get() == 4 => Some(StructRole::H4),
1232-
Self::Heading(n) if n.get() == 5 => Some(StructRole::H5),
1233-
Self::Heading(n) if n.get() == 6 => Some(StructRole::H6),
1234-
Self::Heading(_) => None,
1235-
Self::StructuredHeading => None,
1236-
Self::Title => None,
1237-
Self::FENote => None,
1238-
Self::Sub => None,
1239-
Self::Lbl => Some(StructRole::Lbl),
1240-
Self::Span => Some(StructRole::Span),
1241-
Self::Em => None,
1242-
Self::Strong => None,
1243-
Self::Link => Some(StructRole::Link),
1244-
Self::Annot => Some(StructRole::Annot),
1245-
Self::Form => Some(StructRole::Form),
1246-
Self::Ruby => Some(StructRole::Ruby),
1247-
Self::RB => Some(StructRole::RB),
1248-
Self::RT => Some(StructRole::RT),
1249-
Self::Warichu => Some(StructRole::Warichu),
1250-
Self::WT => Some(StructRole::WT),
1251-
Self::WP => Some(StructRole::WP),
1252-
Self::L => Some(StructRole::L),
1253-
Self::LI => Some(StructRole::LI),
1254-
Self::LBody => Some(StructRole::LBody),
1255-
Self::Table => Some(StructRole::Table),
1256-
Self::TR => Some(StructRole::TR),
1257-
Self::TH => Some(StructRole::TH),
1258-
Self::TD => Some(StructRole::TD),
1259-
Self::THead => Some(StructRole::THead),
1260-
Self::TBody => Some(StructRole::TBody),
1261-
Self::TFoot => Some(StructRole::TFoot),
1262-
Self::Caption => Some(StructRole::Caption),
1263-
Self::Figure => Some(StructRole::Figure),
1264-
Self::Formula => Some(StructRole::Formula),
1265-
Self::Artifact => None,
1266-
}
1267-
}
1268-
1269-
/// Return the closest equivalent role in the PDF 1.7 namespace.
1270-
///
1271-
/// Returns `None` if the role exactly matches a PDF 1.7 role (see
1272-
/// [`Self::into_pdf_1_7`]).
1273-
///
1274-
/// There are three parameters governing the role mapping:
1216+
/// How the type should be represented in PDF 1.7.
12751217
///
1276-
/// - `map_hn_to_h6`: Are headings with levels higher than 6 are mapped to
1277-
/// [`StructRole::H6`] (`true`) or [`StructRole::P`] (`false`)
1278-
/// - `map_title_to_h1`: Is the `Title` role mapped to [`StructRole::H1`]
1279-
/// (`true`) or to [`StructRole::P`] (`false`)
1280-
/// - `map_sub_to_span`: Is the `Sub` role mapped to [`StructRole::Span`]
1281-
/// (`true`) or to [`StructRole::Div`] (`false`)
1282-
pub fn role_mapped_1_7(
1283-
self,
1284-
map_hn_to_h6: bool,
1285-
map_title_to_h1: bool,
1286-
map_sub_to_span: bool,
1287-
) -> Option<StructRole> {
1218+
/// The `opts` parameter allows to control how certain roles are represented
1219+
/// in PDF 1.7. You can also use its default constructor.
1220+
pub fn compatibility_1_7(self, opts: RoleMapOpts) -> StructRole2Compat {
12881221
match self {
1289-
Self::Document => None,
1290-
Self::DocumentFragment => Some(StructRole::Div),
1291-
Self::Part => None,
1292-
Self::Sect => None,
1293-
Self::Div => None,
1294-
Self::Aside => Some(StructRole::Div),
1295-
Self::NonStruct => None,
1296-
Self::P => None,
1297-
Self::Heading(n) if (1u16..=6).contains(&n.get()) => None,
1298-
Self::Heading(_) => {
1299-
Some(if map_hn_to_h6 { StructRole::H6 } else { StructRole::P })
1222+
Self::Document => StructRole2Compat::Compatible(StructRole::Document),
1223+
Self::DocumentFragment => StructRole2Compat::RoleMapping(StructRole::Div),
1224+
Self::Part => StructRole2Compat::Compatible(StructRole::Part),
1225+
Self::Sect => StructRole2Compat::Compatible(StructRole::Sect),
1226+
Self::Div => StructRole2Compat::Compatible(StructRole::Div),
1227+
Self::Aside => StructRole2Compat::RoleMapping(StructRole::Div),
1228+
Self::NonStruct => StructRole2Compat::Compatible(StructRole::NonStruct),
1229+
Self::P => StructRole2Compat::Compatible(StructRole::P),
1230+
Self::Heading(n) if n.get() == 1 => {
1231+
StructRole2Compat::Compatible(StructRole::H1)
1232+
}
1233+
Self::Heading(n) if n.get() == 2 => {
1234+
StructRole2Compat::Compatible(StructRole::H2)
13001235
}
1301-
Self::StructuredHeading => Some(StructRole::P),
1302-
Self::Title => {
1303-
Some(if map_title_to_h1 { StructRole::H1 } else { StructRole::P })
1236+
Self::Heading(n) if n.get() == 3 => {
1237+
StructRole2Compat::Compatible(StructRole::H3)
13041238
}
1305-
Self::FENote => Some(StructRole::Note),
1306-
Self::Sub => {
1307-
Some(if map_sub_to_span { StructRole::Span } else { StructRole::Div })
1239+
Self::Heading(n) if n.get() == 4 => {
1240+
StructRole2Compat::Compatible(StructRole::H4)
13081241
}
1309-
Self::Lbl => None,
1310-
Self::Span => None,
1311-
Self::Em => Some(StructRole::Span),
1312-
Self::Strong => Some(StructRole::Span),
1313-
Self::Link => None,
1314-
Self::Annot => None,
1315-
Self::Form => None,
1316-
Self::Ruby => None,
1317-
Self::RB => None,
1318-
Self::RT => None,
1319-
Self::Warichu => None,
1320-
Self::WT => None,
1321-
Self::WP => None,
1322-
Self::L => None,
1323-
Self::LI => None,
1324-
Self::LBody => None,
1325-
Self::Table => None,
1326-
Self::TR => None,
1327-
Self::TH => None,
1328-
Self::TD => None,
1329-
Self::THead => None,
1330-
Self::TBody => None,
1331-
Self::TFoot => None,
1332-
Self::Caption => None,
1333-
Self::Figure => None,
1334-
Self::Formula => None,
1335-
Self::Artifact => Some(StructRole::Private),
1242+
Self::Heading(n) if n.get() == 5 => {
1243+
StructRole2Compat::Compatible(StructRole::H5)
1244+
}
1245+
Self::Heading(n) if n.get() == 6 => {
1246+
StructRole2Compat::Compatible(StructRole::H6)
1247+
}
1248+
Self::Heading(_) => StructRole2Compat::RoleMapping(
1249+
if opts.contains(RoleMapOpts::map_hn_to_h6) {
1250+
StructRole::H6
1251+
} else {
1252+
StructRole::P
1253+
},
1254+
),
1255+
Self::StructuredHeading => StructRole2Compat::RoleMapping(StructRole::P),
1256+
Self::Title => StructRole2Compat::RoleMapping(
1257+
if opts.contains(RoleMapOpts::map_title_to_h1) {
1258+
StructRole::H1
1259+
} else {
1260+
StructRole::P
1261+
},
1262+
),
1263+
Self::FENote => StructRole2Compat::RoleMapping(StructRole::Note),
1264+
Self::Sub => StructRole2Compat::RoleMapping(
1265+
if opts.contains(RoleMapOpts::map_sub_to_span) {
1266+
StructRole::Span
1267+
} else {
1268+
StructRole::Div
1269+
},
1270+
),
1271+
Self::Lbl => StructRole2Compat::Compatible(StructRole::Lbl),
1272+
Self::Span => StructRole2Compat::Compatible(StructRole::Span),
1273+
Self::Em => StructRole2Compat::RoleMapping(StructRole::Span),
1274+
Self::Strong => StructRole2Compat::RoleMapping(StructRole::Span),
1275+
Self::Link => StructRole2Compat::Compatible(StructRole::Link),
1276+
Self::Annot => StructRole2Compat::Compatible(StructRole::Annot),
1277+
Self::Form => StructRole2Compat::Compatible(StructRole::Form),
1278+
Self::Ruby => StructRole2Compat::Compatible(StructRole::Ruby),
1279+
Self::RB => StructRole2Compat::Compatible(StructRole::RB),
1280+
Self::RT => StructRole2Compat::Compatible(StructRole::RT),
1281+
Self::Warichu => StructRole2Compat::Compatible(StructRole::Warichu),
1282+
Self::WT => StructRole2Compat::Compatible(StructRole::WT),
1283+
Self::WP => StructRole2Compat::Compatible(StructRole::WP),
1284+
Self::L => StructRole2Compat::Compatible(StructRole::L),
1285+
Self::LI => StructRole2Compat::Compatible(StructRole::LI),
1286+
Self::LBody => StructRole2Compat::Compatible(StructRole::LBody),
1287+
Self::Table => StructRole2Compat::Compatible(StructRole::Table),
1288+
Self::TR => StructRole2Compat::Compatible(StructRole::TR),
1289+
Self::TH => StructRole2Compat::Compatible(StructRole::TH),
1290+
Self::TD => StructRole2Compat::Compatible(StructRole::TD),
1291+
Self::THead => StructRole2Compat::Compatible(StructRole::THead),
1292+
Self::TBody => StructRole2Compat::Compatible(StructRole::TBody),
1293+
Self::TFoot => StructRole2Compat::Compatible(StructRole::TFoot),
1294+
Self::Caption => StructRole2Compat::Compatible(StructRole::Caption),
1295+
Self::Figure => StructRole2Compat::Compatible(StructRole::Figure),
1296+
Self::Formula => StructRole2Compat::Compatible(StructRole::Formula),
1297+
Self::Artifact => StructRole2Compat::RoleMapping(StructRole::Private),
13361298
}
13371299
}
13381300

@@ -1377,11 +1339,75 @@ impl StructRole2 {
13771339
}
13781340
}
13791341

1342+
bitflags::bitflags! {
1343+
/// Options for mapping PDF 2.0 [`StructRole2`] to PDF 1.7 [`StructRole`].
1344+
pub struct RoleMapOpts: u8 {
1345+
/// Whether to map headings with levels higher than 6 to [`StructRole::H6`]
1346+
/// (`true`) or [`StructRole::P`] (`false`).
1347+
const map_hn_to_h6 = 1 << 0;
1348+
/// Whether to map the `Title` role to [`StructRole::H1`] (`true`) or
1349+
/// [`StructRole::P`] (`false`).
1350+
const map_title_to_h1 = 1 << 1;
1351+
/// Whether to map the `Sub` role to [`StructRole::Span`] (`true`) or
1352+
/// [`StructRole::Div`] (`false`).
1353+
const map_sub_to_span = 1 << 2;
1354+
}
1355+
}
1356+
1357+
impl Default for RoleMapOpts {
1358+
fn default() -> Self {
1359+
Self::empty()
1360+
}
1361+
}
1362+
1363+
/// How a particular PDF 2.0 [`StructRole2`] can be represented in PDF 1.7.
1364+
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1365+
pub enum StructRole2Compat {
1366+
/// The role is a direct match with this PDF 1.7 role.
1367+
Compatible(StructRole),
1368+
/// The has no direct match with a PDF 1.7 role, but can be mapped to
1369+
/// a PDF 1.7 role with a similar meaning.
1370+
RoleMapping(StructRole),
1371+
}
1372+
1373+
impl StructRole2Compat {
1374+
/// Return the corresponding PDF 1.7 [`StructRole`] for this role or `None`
1375+
/// if there is no exact match.
1376+
pub fn into_pdf_1_7(self) -> Option<StructRole> {
1377+
match self {
1378+
Self::Compatible(role) => Some(role),
1379+
Self::RoleMapping(_) => None,
1380+
}
1381+
}
1382+
1383+
/// Return the closest equivalent role in the PDF 1.7 namespace.
1384+
///
1385+
/// Returns `None` if the role exactly matches a PDF 1.7 role (see
1386+
/// [`Self::into_pdf_1_7`]).
1387+
pub fn role_mapped_1_7(self) -> Option<StructRole> {
1388+
match self {
1389+
Self::Compatible(_) => None,
1390+
Self::RoleMapping(role) => Some(role),
1391+
}
1392+
}
1393+
1394+
/// Return the inner role.
1395+
pub fn role(self) -> StructRole {
1396+
match self {
1397+
Self::Compatible(role) => role,
1398+
Self::RoleMapping(role) => role,
1399+
}
1400+
}
1401+
}
1402+
13801403
impl TryFrom<StructRole2> for StructRole {
13811404
type Error = ();
13821405

13831406
fn try_from(value: StructRole2) -> Result<Self, Self::Error> {
1384-
value.into_pdf_1_7().ok_or(())
1407+
value
1408+
.compatibility_1_7(RoleMapOpts::default())
1409+
.into_pdf_1_7()
1410+
.ok_or(())
13851411
}
13861412
}
13871413

0 commit comments

Comments
 (0)