Skip to content
Open
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
19 changes: 17 additions & 2 deletions core/fpdfdoc/cpdf_appearancegenerator_freetext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,15 @@ PrepareRichFreeTextAPInternal(CPDF_Document* doc,
prepared->footprint =
inline_rotation ? envelope.box_rot.matrix.TransformRect(box) : box;
} else {
const BorderStyleInfo border_style_info =
GetBorderStyleInfo(annot_dict->GetDictFor("BS"));
RetainPtr<const CPDF_Dictionary> border_style =
annot_dict->GetDictFor("BS");
BorderStyleInfo border_style_info =
GetBorderStyleInfo(border_style.Get());
if (!border_style) {
// No /BS: the /Border array's width (ISO 32000-1 12.5.2), as the
// callout reads it. PDF.js writes /Border [0 0 0] for no border.
border_style_info.width = GetBorderWidth(annot_dict);
}
const ShapeRotationInfo rot_info = GetShapeRotationInfo(annot_dict);
const CFX_FloatRect rect = rot_info.bbox;
const float half_border_width = border_style_info.width / 2.0f;
Expand Down Expand Up @@ -448,6 +455,14 @@ PrepareRichFreeTextAPInternal(CPDF_Document* doc,
if (text_area.Width() > 0 && text_area.Height() > 0) {
CPDF_RichTextLayout::Options options;
options.typographic_features = doc->GetTypographicFeaturesEnabled();
// A plain box (no /RC) without a border is how PDF.js writes free text,
// with no appearance: draw it as PDF.js does, its lines broken only at
// line breaks and shrunk to fit, reading in its text's direction.
if (in.document->source == CPDF_RichTextDocument::Source::kContents &&
!annot_dict->KeyExist("RC") && text_area == box) {
options.shrink_to_fit = true;
options.direction_from_text = true;
}
CPDF_RichTextLayout layout(&map, options);
arranged =
layout.Arrange(*in.document, text_area, GetVerticalAlign(annot_dict));
Expand Down
35 changes: 35 additions & 0 deletions core/fpdfdoc/cpdf_appearancegenerator_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "core/fpdfapi/parser/cpdf_stream.h"
#include "core/fpdfapi/parser/cpdf_string.h"
#include "core/fpdfapi/parser/cpdf_test_document.h"
#include "core/fxcrt/fx_string.h"
#include "core/fxge/cfx_color.h"
#include "testing/gtest/include/gtest/gtest.h"

Expand Down Expand Up @@ -207,6 +208,40 @@ TEST_F(CPDFGenerateAPTest, GenerateEphemeralFreeTextAPDoesNotCreateAcroForm) {
EXPECT_FALSE(annot_dict->KeyExist(pdfium::annotation::kAP));
}

// A plain box with /Border [0 0 0], as PDF.js writes free text: no border,
// and a line too wide for the box is drawn smaller instead of wrapping.
TEST_F(CPDFGenerateAPTest, FreeTextWithoutBorderShrinksToFit) {
CPDF_TestDocument doc;
doc.CreateNewDoc();
auto annot_dict = MakeAnnotDict("FreeText", CFX_FloatRect(0, 0, 40, 16));
annot_dict->SetNewFor<CPDF_String>("DA", "/Helv 12 Tf 0 g");
annot_dict->SetNewFor<CPDF_String>(pdfium::annotation::kContents,
"hello world");
annot_dict->SetFor(pdfium::annotation::kBorder, MakeNumberArray({0, 0, 0}));

std::optional<CPDF_AppearanceGenerator::GeneratedAP> generated =
CPDF_AppearanceGenerator::GenerateEphemeralAnnotAP(
&doc, annot_dict.Get(), CPDF_Annot::Subtype::FREETEXT);
ASSERT_TRUE(generated.has_value());
ASSERT_TRUE(generated->normal_stream);
ASSERT_TRUE(generated->normal_stream->IsMemoryBased());
const pdfium::span<const uint8_t> data =
generated->normal_stream->GetInMemoryRawData();
const ByteString content(data.data(), data.size());

EXPECT_FALSE(content.Contains(" w\n")); // no border stroke
// Drawn below 12 pt.
const std::optional<size_t> tf = content.Find(" Tf\n");
ASSERT_TRUE(tf.has_value());
const ByteString before_tf = content.First(tf.value());
const std::optional<size_t> space = before_tf.ReverseFind(' ');
ASSERT_TRUE(space.has_value());
const float size =
StringToFloat(before_tf.Substr(space.value() + 1).AsStringView());
EXPECT_GT(size, 0.0f);
EXPECT_LT(size, 12.0f);
}

TEST_F(CPDFGenerateAPTest,
GeneratePersistentFreeTextAPAfterDefaultAppearanceColorUpdate) {
CPDF_TestDocument doc;
Expand Down
12 changes: 12 additions & 0 deletions core/fpdfdoc/cpdf_richtextlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@ CPDF_RichTextLayout::Result CPDF_RichTextLayout::Arrange(
} else {
candidate_sizes.push_back(document.body.size);
}
const bool shrink_to_fit = options_.shrink_to_fit && !auto_size;

// Faces resolve once per request (family, weight, italic) over every
// character styled with it, whatever the size attempt (C note §1.3): a
Expand Down Expand Up @@ -939,6 +940,7 @@ CPDF_RichTextLayout::Result CPDF_RichTextLayout::Arrange(
// The furthest opportunity whose ink fits.
size_t best = length;
bool fits_whole =
shrink_to_fit ||
Impl::InkWidth(segment, pos, length) <= avail + kEpsilon;
if (!fits_whole) {
best = 0;
Expand Down Expand Up @@ -1130,6 +1132,16 @@ CPDF_RichTextLayout::Result CPDF_RichTextLayout::Arrange(
widest > area_width + kEpsilon)) {
continue; // try the next smaller size
}
if (shrink_to_fit && attempt == 0 && content_height > 0 && widest > 0 &&
(content_height > area_height + kEpsilon ||
widest > area_width + kEpsilon)) {
const float scale =
std::min(area_width / widest, area_height / content_height);
if (scale > 0) {
candidate_sizes.push_back(body_size * scale);
continue; // once more at the size that fits
}
}

// 7. Vertical alignment of the block, then absolute baselines.
float top = 0;
Expand Down
4 changes: 4 additions & 0 deletions core/fpdfdoc/cpdf_richtextlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ class CPDF_RichTextLayout {
// A paragraph reads in the direction of its first strong character (left
// to right when it has none), as a form field does, instead of its dir.
bool direction_from_text = false;
// Lines break only at line breaks, and a text wider or taller than the
// area is drawn smaller to fit, by the smaller of the two ratios (as
// PDF.js draws a plain free text box). Not for an auto size (size 0).
bool shrink_to_fit = false;
};

struct Result {
Expand Down