Skip to content

Commit efc511b

Browse files
authored
ROB: Tolerate non-array outline color when reading and cloning (#3870)
1 parent 6b06dcf commit efc511b

3 files changed

Lines changed: 48 additions & 6 deletions

File tree

pypdf/_doc_common.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,15 @@ def _build_outline_item(self, node: DictionaryObject) -> Optional[Destination]:
10501050
if outline_item:
10511051
if "/C" in node:
10521052
# Color of outline item font in (R, G, B) with values ranging 0.0-1.0
1053-
outline_item[NameObject("/C")] = ArrayObject(FloatObject(c) for c in node["/C"]) # type: ignore[attr-defined]
1053+
color = node["/C"]
1054+
if isinstance(color, list):
1055+
outline_item[NameObject("/C")] = ArrayObject(FloatObject(c) for c in color)
1056+
else:
1057+
logger_warning(
1058+
"Ignoring non-array outline color %(color)r",
1059+
source=__name__,
1060+
color=color,
1061+
)
10541062
if "/F" in node:
10551063
# specifies style characteristics bold and/or italic
10561064
# with 1=italic, 2=bold, 3=both

pypdf/_writer.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3139,11 +3139,10 @@ def _clone_outline(self, dest: Destination) -> TreeObject:
31393139
# TODO: /SE
31403140
if dest.node is not None:
31413141
n_ol[NameObject("/F")] = NumberObject(dest.node.get("/F", 0))
3142-
n_ol[NameObject("/C")] = ArrayObject(
3143-
dest.node.get(
3144-
"/C", [FloatObject(0.0), FloatObject(0.0), FloatObject(0.0)]
3145-
)
3146-
)
3142+
color = dest.node.get("/C", NullObject()).get_object()
3143+
if not isinstance(color, list):
3144+
color = [FloatObject(0.0), FloatObject(0.0), FloatObject(0.0)]
3145+
n_ol[NameObject("/C")] = ArrayObject(color)
31473146
return n_ol
31483147

31493148
def _insert_filtered_outline(

tests/test_doc_common.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,3 +694,38 @@ def test_get_qualified_field_name__cyclic() -> None:
694694
match=r"^Detected cycle in /Parent hierarchy when retrieving qualified field name\.$"
695695
):
696696
_ = writer._get_qualified_field_name(parent=dictionary1)
697+
698+
699+
def test_build_outline_item__non_array_color(caplog):
700+
"""A malformed outline item whose /C is not an array must not crash."""
701+
writer = PdfWriter()
702+
writer.add_blank_page(72, 72)
703+
704+
item = DictionaryObject()
705+
writer._add_object(item)
706+
item.update({
707+
NameObject("/Title"): TextStringObject("Bad color"),
708+
NameObject("/Dest"): ArrayObject(
709+
[writer.pages[0].indirect_reference, NameObject("/Fit")]
710+
),
711+
NameObject("/C"): NumberObject(5), # should be an [r, g, b] array
712+
})
713+
outlines = DictionaryObject()
714+
writer._add_object(outlines)
715+
outlines.update({
716+
NameObject("/Type"): NameObject("/Outlines"),
717+
NameObject("/First"): item.indirect_reference,
718+
NameObject("/Last"): item.indirect_reference,
719+
})
720+
item[NameObject("/Parent")] = outlines.indirect_reference
721+
writer._root_object[NameObject("/Outlines")] = outlines.indirect_reference
722+
723+
outline = writer.outline
724+
assert outline[0]["/Title"] == "Bad color"
725+
assert "/C" not in outline[0]
726+
assert any("outline color" in message for message in caplog.messages)
727+
728+
# The same value is read again, unsanitised, while cloning the outline
729+
# during append, so that path must tolerate it too.
730+
target = PdfWriter()
731+
target.append(writer)

0 commit comments

Comments
 (0)