Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions core/fpdfdoc/cpdf_appearancegenerator_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ std::vector<float> DashValues(const CPDF_Array* dash_array) {
return values;
}

// A dashed /BS's pattern (ISO 32000 table 168): its /D, or the default [3]
// when it names none. An empty /D is a solid line (8.4.3.6): no pattern.
std::vector<float> BorderStyleDash(const CPDF_Dictionary* border_style_dict) {
RetainPtr<const CPDF_Array> dash_array = border_style_dict->GetArrayFor("D");
if (!dash_array) {
return {kDefaultDash};
}
return DashValues(dash_array.Get());
}

// The dash operator for |values|; empty when there are none.
ByteString DashOperator(const std::vector<float>& values) {
if (values.empty()) {
Expand Down Expand Up @@ -302,10 +312,12 @@ BorderStyleInfo GetBorderStyleInfo(const CPDF_Dictionary* border_style_dict) {
}
}

std::vector<float> dash =
DashValues(border_style_dict->GetArrayFor("D").Get());
// A dashed border with an empty pattern draws a solid line.
std::vector<float> dash = BorderStyleDash(border_style_dict);
if (!dash.empty()) {
border_style_info.dash_pattern = std::move(dash);
} else if (border_style_info.style == BorderStyle::kDash) {
border_style_info.style = BorderStyle::kSolid;
}

return border_style_info;
Expand Down Expand Up @@ -551,20 +563,16 @@ float GetBorderWidth(const CPDF_Dictionary* dict) {
return 1.0f;
}

// The dash operator a border draws with: a dashed /BS draws its /D, or the
// default dash when it names none; a /Border draws its own dash array.
// The dash operator a border draws with, as ISO 32000 reads it: a dashed /BS
// draws its /D, or the default dash when it names none, and nothing (a solid
// line) for an empty one; a /Border draws its own dash array.
ByteString GetDashPatternString(const CPDF_Dictionary* dict) {
RetainPtr<const CPDF_Dictionary> border_style_dict = dict->GetDictFor("BS");
if (border_style_dict) {
if (border_style_dict->GetByteStringFor("S") != "D") {
return ByteString();
}
std::vector<float> dash =
DashValues(border_style_dict->GetArrayFor("D").Get());
if (dash.empty()) {
dash.push_back(kDefaultDash);
}
return DashOperator(dash);
return DashOperator(BorderStyleDash(border_style_dict.Get()));
}

RetainPtr<const CPDF_Array> border_array =
Expand Down
7 changes: 7 additions & 0 deletions fpdfsdk/epdf_annot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,13 @@ EPDFAnnot_GetBorderStyle(FPDF_ANNOTATION annot, float* width) {
if (nStyle == CPDF_Annot::BorderStyle::kUnknown) {
nStyle = CPDF_Annot::BorderStyle::kSolid;
}
// A dashed border with an empty pattern draws a solid line (ISO 32000
// 8.4.3.6), so it reads solid, as a /Border with an empty dash array does.
RetainPtr<const CPDF_Array> pDashArray = pBSDict->GetArrayFor("D");
if (nStyle == CPDF_Annot::BorderStyle::kDashed && pDashArray &&
pDashArray->IsEmpty()) {
nStyle = CPDF_Annot::BorderStyle::kSolid;
}
return static_cast<FPDF_ANNOT_BORDER_STYLE>(nStyle);
}

Expand Down
74 changes: 74 additions & 0 deletions fpdfsdk/epdf_annot_embeddertest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4353,6 +4353,13 @@ TEST_F(EPDFAnnotEmbedderTest, TextFieldDrawsItsBorderStyle) {
EXPECT_THAT(look, HasSubstr(L"] 0 d"));
EXPECT_THAT(look, Not(HasSubstr(L" re s")));

// An empty pattern is a solid line (ISO 32000 8.4.3.6).
bs->SetNewFor<CPDF_Array>("D");
ASSERT_TRUE(EPDFAnnot_GenerateFormFieldAP(annot.get()));
look = GetNormalAppearance(annot.get());
EXPECT_THAT(look, HasSubstr(L" re s"));
EXPECT_THAT(look, Not(HasSubstr(L"] 0 d")));

bs->SetNewFor<CPDF_Name>("S", "U");
ASSERT_TRUE(EPDFAnnot_GenerateFormFieldAP(annot.get()));
look = GetNormalAppearance(annot.get());
Expand Down Expand Up @@ -7601,6 +7608,73 @@ TEST_F(EPDFAnnotEmbedderTest, DashedBorderDrawsItsDashOrTheDefault) {
.has_value());
}

// An empty dash array is a solid line (ISO 32000 8.4.3.6), in /BS /D as in
// /Border: the border draws no dash and reads solid. A /D left out is the
// default dash, [3].
TEST_F(EPDFAnnotEmbedderTest, AnEmptyDashPatternIsASolidLine) {
embedpdf_test::ScopedTestDocument doc(NewTestDocument());
ASSERT_TRUE(doc);
ScopedFPDFPage page(FPDFPage_New(doc.get(), 0, 200, 200));
ASSERT_TRUE(page);

for (int subtype : {FPDF_ANNOT_SQUARE, FPDF_ANNOT_CIRCLE, FPDF_ANNOT_LINE,
FPDF_ANNOT_INK}) {
SCOPED_TRACE(subtype);
ScopedFPDFAnnotation annot(FPDFPage_CreateAnnot(page.get(), subtype));
ASSERT_TRUE(annot);
const FS_RECTF rect{/*left=*/10.0f, /*top=*/90.0f, /*right=*/90.0f,
/*bottom=*/10.0f};
ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect));
ASSERT_TRUE(
EPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, 0, 0, 0));
if (subtype == FPDF_ANNOT_LINE) {
const FS_POINTF start{20, 20};
const FS_POINTF end{80, 80};
ASSERT_TRUE(EPDFAnnot_SetLine(annot.get(), &start, &end));
} else if (subtype == FPDF_ANNOT_INK) {
static constexpr FS_POINTF kStroke[] = {{20, 20}, {50, 80}, {80, 30}};
ASSERT_EQ(
0, FPDFAnnot_AddInkStroke(annot.get(), kStroke, std::size(kStroke)));
}
ASSERT_TRUE(EPDFAnnot_SetBorderStyle(annot.get(), FPDF_ANNOT_BS_DASHED, 2));
RetainPtr<CPDF_Dictionary> annot_dict =
CPDFAnnotContextFromFPDFAnnotation(annot.get())->GetMutableAnnotDict();
RetainPtr<CPDF_Dictionary> bs = annot_dict->GetMutableDictFor("BS");
ASSERT_TRUE(bs);

// No /D: the default dash.
EXPECT_EQ(FPDF_ANNOT_BS_DASHED,
EPDFAnnot_GetBorderStyle(annot.get(), nullptr));
ASSERT_TRUE(EPDFAnnot_GenerateAppearance(annot.get()));
EXPECT_TRUE(GetNormalAppearanceStreamBytes(annot.get())
.Find("[3 ] 0 d")
.has_value());

// An empty /D: solid.
bs->SetNewFor<CPDF_Array>("D");
EXPECT_EQ(FPDF_ANNOT_BS_SOLID,
EPDFAnnot_GetBorderStyle(annot.get(), nullptr));
EXPECT_EQ(0u, EPDFAnnot_GetBorderDashPatternCount(annot.get()));
ASSERT_TRUE(EPDFAnnot_GenerateAppearance(annot.get()));
EXPECT_FALSE(
GetNormalAppearanceStreamBytes(annot.get()).Find("] 0 d").has_value());

// A /Border whose dash array is empty, without /BS: solid too.
annot_dict->RemoveFor("BS");
RetainPtr<CPDF_Array> border =
annot_dict->SetNewFor<CPDF_Array>(pdfium::annotation::kBorder);
border->AppendNew<CPDF_Number>(0);
border->AppendNew<CPDF_Number>(0);
border->AppendNew<CPDF_Number>(2);
border->AppendNew<CPDF_Array>();
EXPECT_EQ(FPDF_ANNOT_BS_SOLID,
EPDFAnnot_GetBorderStyle(annot.get(), nullptr));
ASSERT_TRUE(EPDFAnnot_GenerateAppearance(annot.get()));
EXPECT_FALSE(
GetNormalAppearanceStreamBytes(annot.get()).Find("] 0 d").has_value());
}
}

TEST_F(EPDFAnnotEmbedderTest, SetNameAcceptsAnyNameAndKeepsAppearance) {
embedpdf_test::ScopedTestDocument doc(NewTestDocument());
ASSERT_TRUE(doc);
Expand Down
4 changes: 3 additions & 1 deletion public/epdf_annot.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@ EPDFAnnot_SetNoColor(FPDF_ANNOTATION annot, FPDFANNOT_COLORTYPE type);

// Experimental EmbedPDF Extension API.
// Get the border style and width of an annotation. This function handles both
// the modern /BS dictionary and the legacy /Border array.
// the modern /BS dictionary and the legacy /Border array. A border is dashed
// when it draws dashes: a dashed /BS whose /D is an empty array, like a
// /Border whose dash array is empty, draws a solid line and reads solid.
//
// annot - handle to an annotation.
// width - receives the border width; can be NULL.
Expand Down
1 change: 1 addition & 0 deletions testing/embedpdf_layer_mode_writers.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ EPDFActionEmbedderTest.NodeFilePathAndNamePayloadsFromSyntheticDicts 2
EPDFActionEmbedderTest.OpenActionDestinationForms 3
EPDFActionEmbedderTest.ResetFormThreeStates 1
EPDFActionEmbedderTest.SetNextChainsActions 3
EPDFAnnotEmbedderTest.AnEmptyDashPatternIsASolidLine 15
EPDFAnnotEmbedderTest.AnnotFontMapStagesEveryNamedFont 16
EPDFAnnotEmbedderTest.ApplyPageRedactionsCountsRemovedAnnotations 6
EPDFAnnotEmbedderTest.ApplyPageRedactionsTwoMarksOnOneTextObject 3
Expand Down