Fix rendering for freetext_annotation_with_da.in
Add GenerateFreeTextAP() and use it to handle freetext annotations
without appearance streams in CPDF_GenerateAP::GenerateAnnotAP(). Since
GenerateFreeTextAP() reuses a lot of code from GenerateFormAP(),
refactor some common bits into helpers. This is a stepping stone towards
fixing freetext_annotation_without_da.pdf.
Bug: 380434959
Change-Id: I6c16131f711b4b011766165518d15c08ab8a9480
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/130010
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
diff --git a/core/fpdfdoc/cpdf_generateap.cpp b/core/fpdfdoc/cpdf_generateap.cpp
index e2d704c..bd7b446 100644
--- a/core/fpdfdoc/cpdf_generateap.cpp
+++ b/core/fpdfdoc/cpdf_generateap.cpp
@@ -105,6 +105,14 @@
return ByteString(font_stream);
}
+void SetVtFontSize(float font_size, CPVT_VariableText& vt) {
+ if (FXSYS_IsFloatZero(font_size)) {
+ vt.SetAutoFontSize(true);
+ } else {
+ vt.SetFontSize(font_size);
+ }
+}
+
// ISO 32000-1:2008 spec, table 166.
// ISO 32000-2:2020 spec, table 168.
struct BorderStyleInfo {
@@ -251,6 +259,32 @@
return default_appearance_string;
}
+struct DefaultAppearanceInfo {
+ ByteString font_name;
+ float font_size;
+ CFX_Color text_color;
+};
+
+std::optional<DefaultAppearanceInfo> GetDefaultAppearanceInfo(
+ const ByteString& default_appearance_string) {
+ if (default_appearance_string.IsEmpty()) {
+ return std::nullopt;
+ }
+
+ CPDF_DefaultAppearance appearance(default_appearance_string);
+
+ float font_size = 0;
+ std::optional<ByteString> font = appearance.GetFont(&font_size);
+ if (!font.has_value()) {
+ return std::nullopt;
+ }
+
+ return DefaultAppearanceInfo{
+ .font_name = font.value(),
+ .font_size = font_size,
+ .text_color = fpdfdoc::CFXColorFromString(default_appearance_string)};
+}
+
bool CloneResourcesDictIfMissingFromStream(CPDF_Dictionary* stream_dict,
const CPDF_Dictionary* dr_dict) {
RetainPtr<CPDF_Dictionary> resources_dict =
@@ -599,6 +633,22 @@
return font_dict;
}
+RetainPtr<CPDF_Dictionary> GetFontFromDrFontDictOrGenerateFallback(
+ CPDF_Document* doc,
+ CPDF_Dictionary* dr_font_dict,
+ const ByteString& font_name) {
+ RetainPtr<CPDF_Dictionary> font_dict =
+ dr_font_dict->GetMutableDictFor(font_name);
+ if (font_dict) {
+ return font_dict;
+ }
+
+ RetainPtr<CPDF_Dictionary> new_font_dict = GenerateFallbackFontDict(doc);
+ dr_font_dict->SetNewFor<CPDF_Reference>(font_name, doc,
+ new_font_dict->GetObjNum());
+ return new_font_dict;
+}
+
RetainPtr<CPDF_Dictionary> GenerateResourceFontDict(
CPDF_Document* doc,
const ByteString& font_name,
@@ -742,11 +792,7 @@
const uint32_t max_len = max_len_field ? max_len_field->GetInteger() : 0;
vt.SetPlateRect(body_rect);
vt.SetAlignment(align);
- if (FXSYS_IsFloatZero(font_size)) {
- vt.SetAutoFontSize(true);
- } else {
- vt.SetFontSize(font_size);
- }
+ SetVtFontSize(font_size, vt);
bool is_multi_line = (flags >> 12) & 1;
if (is_multi_line) {
@@ -795,11 +841,7 @@
edit_rect.right = button_rect.left;
edit_rect.Normalize();
vt.SetPlateRect(edit_rect);
- if (FXSYS_IsFloatZero(font_size)) {
- vt.SetAutoFontSize(true);
- } else {
- vt.SetFontSize(font_size);
- }
+ SetVtFontSize(font_size, vt);
vt.Initialize();
vt.SetText(value);
@@ -1000,6 +1042,109 @@
return true;
}
+bool GenerateFreeTextAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
+ RetainPtr<CPDF_Dictionary> root_dict = doc->GetMutableRoot();
+ if (!root_dict) {
+ return false;
+ }
+
+ RetainPtr<CPDF_Dictionary> form_dict =
+ root_dict->GetMutableDictFor("AcroForm");
+ if (!form_dict) {
+ return false;
+ }
+
+ std::optional<DefaultAppearanceInfo> default_appearance_info =
+ GetDefaultAppearanceInfo(
+ GetDefaultAppearanceString(annot_dict, form_dict));
+ if (!default_appearance_info.has_value()) {
+ return false;
+ }
+
+ RetainPtr<CPDF_Dictionary> dr_dict = form_dict->GetMutableDictFor("DR");
+ if (!dr_dict) {
+ return false;
+ }
+
+ RetainPtr<CPDF_Dictionary> dr_font_dict = dr_dict->GetMutableDictFor("Font");
+ if (!ValidateFontResourceDict(dr_font_dict.Get())) {
+ return false;
+ }
+
+ const ByteString& font_name = default_appearance_info.value().font_name;
+ RetainPtr<CPDF_Dictionary> font_dict =
+ GetFontFromDrFontDictOrGenerateFallback(doc, dr_font_dict, font_name);
+ auto* doc_page_data = CPDF_DocPageData::FromDocument(doc);
+ RetainPtr<CPDF_Font> default_font = doc_page_data->GetFont(font_dict);
+ if (!default_font) {
+ return false;
+ }
+
+ fxcrt::ostringstream appearance_stream;
+ appearance_stream << "/" << kGSDictName << " gs ";
+
+ const BorderStyleInfo border_style_info =
+ GetBorderStyleInfo(annot_dict->GetDictFor("BS"));
+ CFX_FloatRect rect = annot_dict->GetRectFor(pdfium::annotation::kRect);
+ const float half_border_width = border_style_info.width / 2.0f;
+ CFX_FloatRect background_rect = rect;
+ background_rect.Deflate(half_border_width, half_border_width);
+ CFX_FloatRect body_rect = background_rect;
+ body_rect.Deflate(half_border_width, half_border_width);
+
+ auto color_array = annot_dict->GetArrayFor(pdfium::annotation::kC);
+ if (color_array) {
+ CFX_Color color = fpdfdoc::CFXColorFromArray(*color_array);
+ appearance_stream << "q\n" << GenerateColorAP(color, PaintOperation::kFill);
+ WriteRect(appearance_stream, background_rect) << " re f\nQ\n";
+ }
+
+ const CFX_Color& text_color = default_appearance_info.value().text_color;
+ const ByteString border_stream =
+ GenerateBorderAP(rect, border_style_info, text_color);
+ if (border_stream.GetLength() > 0) {
+ appearance_stream << "q\n" << border_stream << "Q\n";
+ }
+
+ CPVT_FontMap map(doc, nullptr, std::move(default_font), font_name);
+ CPVT_VariableText::Provider provider(&map);
+ CPVT_VariableText vt(&provider);
+
+ vt.SetPlateRect(body_rect);
+ vt.SetAlignment(annot_dict->GetIntegerFor("Q"));
+ SetVtFontSize(default_appearance_info.value().font_size, vt);
+
+ vt.Initialize();
+ vt.SetText(annot_dict->GetUnicodeTextFor(pdfium::annotation::kContents));
+ vt.RearrangeAll();
+ const CFX_FloatRect content_rect = vt.GetContentRect();
+ CFX_PointF offset(0.0f, (content_rect.Height() - body_rect.Height()) / 2.0f);
+ const ByteString body =
+ GenerateEditAP(vt.GetProvider()->GetFontMap(), vt.GetIterator(), offset,
+ /*continuous=*/true, /*sub_word=*/0);
+ if (body.GetLength() > 0) {
+ appearance_stream << "/Tx BMC\n" << "q\n";
+ if (content_rect.Width() > body_rect.Width() ||
+ content_rect.Height() > body_rect.Height()) {
+ WriteRect(appearance_stream, body_rect) << " re\nW\nn\n";
+ }
+ appearance_stream << "BT\n"
+ << GenerateColorAP(text_color, PaintOperation::kFill)
+ << body << "ET\n"
+ << "Q\nEMC\n";
+ }
+
+ auto graphics_state_dict = GenerateExtGStateDict(*annot_dict, "Normal");
+ auto resource_font_dict =
+ GenerateResourceFontDict(doc, font_name, font_dict->GetObjNum());
+ auto resource_dict = GenerateResourcesDict(
+ doc, std::move(graphics_state_dict), std::move(resource_font_dict));
+ GenerateAndSetAPDict(doc, annot_dict, &appearance_stream,
+ std::move(resource_dict),
+ /*is_text_markup_annotation=*/false);
+ return true;
+}
+
bool GenerateHighlightAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
@@ -1311,22 +1456,13 @@
return;
}
- const ByteString default_appearance_string =
- GetDefaultAppearanceString(annot_dict, form_dict);
- if (default_appearance_string.IsEmpty()) {
+ std::optional<DefaultAppearanceInfo> default_appearance_info =
+ GetDefaultAppearanceInfo(
+ GetDefaultAppearanceString(annot_dict, form_dict));
+ if (!default_appearance_info.has_value()) {
return;
}
- CPDF_DefaultAppearance appearance(default_appearance_string);
-
- float font_size = 0;
- std::optional<ByteString> font = appearance.GetFont(&font_size);
- if (!font.has_value())
- return;
-
- ByteString font_name = font.value();
-
- CFX_Color text_color = fpdfdoc::CFXColorFromString(default_appearance_string);
RetainPtr<CPDF_Dictionary> dr_dict = form_dict->GetMutableDictFor("DR");
if (!dr_dict) {
return;
@@ -1337,13 +1473,9 @@
return;
}
+ const ByteString& font_name = default_appearance_info.value().font_name;
RetainPtr<CPDF_Dictionary> font_dict =
- dr_font_dict->GetMutableDictFor(font_name);
- if (!font_dict) {
- font_dict = GenerateFallbackFontDict(doc);
- dr_font_dict->SetNewFor<CPDF_Reference>(font_name, doc,
- font_dict->GetObjNum());
- }
+ GetFontFromDrFontDictOrGenerateFallback(doc, dr_font_dict, font_name);
auto* doc_page_data = CPDF_DocPageData::FromDocument(doc);
RetainPtr<CPDF_Font> default_font = doc_page_data->GetFont(font_dict);
if (!default_font) {
@@ -1393,6 +1525,8 @@
ap_dict->SetNewFor<CPDF_Reference>("N", doc, normal_stream->GetObjNum());
}
+ const float font_size = default_appearance_info.value().font_size;
+ const CFX_Color& text_color = default_appearance_info.value().text_color;
CPVT_FontMap map(doc, std::move(resources_dict), std::move(default_font),
font_name);
CPVT_VariableText::Provider provider(&map);
@@ -1463,6 +1597,8 @@
switch (subtype) {
case CPDF_Annot::Subtype::CIRCLE:
return GenerateCircleAP(doc, annot_dict);
+ case CPDF_Annot::Subtype::FREETEXT:
+ return GenerateFreeTextAP(doc, annot_dict);
case CPDF_Annot::Subtype::HIGHLIGHT:
return GenerateHighlightAP(doc, annot_dict);
case CPDF_Annot::Subtype::INK:
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected.pdf.0.png
index f97e340..feb9983 100644
--- a/testing/resources/pixel/freetext_annotation_with_da_expected.pdf.0.png
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_mac.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_mac.pdf.0.png
new file mode 100644
index 0000000..1469799
--- /dev/null
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png
new file mode 100644
index 0000000..a77cd69
--- /dev/null
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png
new file mode 100644
index 0000000..04e4110
--- /dev/null
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png
new file mode 100644
index 0000000..66be929
--- /dev/null
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png
Binary files differ