blob: 55c93bf76aee7606716d4e2b811e30a69f0df0d0 [file] [log] [blame] [edit]
// Copyright 2017 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "fpdfsdk/cpdfsdk_appstream.h"
#include <math.h>
#include <iterator>
#include <memory>
#include <sstream>
#include <utility>
#include "constants/appearance.h"
#include "constants/form_flags.h"
#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
#include "core/fpdfapi/font/cpdf_font.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfapi/parser/cpdf_name.h"
#include "core/fpdfapi/parser/cpdf_number.h"
#include "core/fpdfapi/parser/cpdf_reference.h"
#include "core/fpdfapi/parser/cpdf_stream.h"
#include "core/fpdfapi/parser/cpdf_string.h"
#include "core/fpdfapi/parser/fpdf_parser_decode.h"
#include "core/fpdfapi/parser/fpdf_parser_utility.h"
#include "core/fpdfdoc/cpdf_bafontmap.h"
#include "core/fpdfdoc/cpdf_formcontrol.h"
#include "core/fpdfdoc/cpdf_icon.h"
#include "core/fpdfdoc/cpvt_word.h"
#include "core/fxcrt/fx_string_wrappers.h"
#include "fpdfsdk/cpdfsdk_formfillenvironment.h"
#include "fpdfsdk/cpdfsdk_interactiveform.h"
#include "fpdfsdk/cpdfsdk_pageview.h"
#include "fpdfsdk/cpdfsdk_widget.h"
#include "fpdfsdk/pwl/cpwl_edit.h"
#include "fpdfsdk/pwl/cpwl_edit_impl.h"
#include "fpdfsdk/pwl/cpwl_wnd.h"
#include "third_party/base/containers/span.h"
#include "third_party/base/numerics/safe_conversions.h"
namespace {
// Checkbox & radiobutton styles.
enum class CheckStyle { kCheck = 0, kCircle, kCross, kDiamond, kSquare, kStar };
// Pushbutton layout styles.
enum class ButtonStyle {
kLabel = 0,
kIcon,
kIconTopLabelBottom,
kIconBottomLabelTop,
kIconLeftLabelRight,
kIconRightLabelLeft,
kLabelOverIcon
};
const char kAppendRectOperator[] = "re";
const char kConcatMatrixOperator[] = "cm";
const char kCurveToOperator[] = "c";
const char kEndPathNoFillOrStrokeOperator[] = "n";
const char kFillOperator[] = "f";
const char kFillEvenOddOperator[] = "f*";
const char kInvokeNamedXObjectOperator[] = "Do";
const char kLineToOperator[] = "l";
const char kMarkedSequenceBeginOperator[] = "BMC";
const char kMarkedSequenceEndOperator[] = "EMC";
const char kMoveTextPositionOperator[] = "Td";
const char kMoveToOperator[] = "m";
const char kSetCMYKOperator[] = "k";
const char kSetCMKYStrokedOperator[] = "K";
const char kSetDashOperator[] = "d";
const char kSetGrayOperator[] = "g";
const char kSetGrayStrokedOperator[] = "G";
const char kSetLineCapStyleOperator[] = "J";
const char kSetLineJoinStyleOperator[] = "j";
const char kSetLineWidthOperator[] = "w";
const char kSetNonZeroWindingClipOperator[] = "W";
const char kSetRGBOperator[] = "rg";
const char kSetRGBStrokedOperator[] = "RG";
const char kSetTextFontAndSizeOperator[] = "Tf";
const char kShowTextOperator[] = "Tj";
const char kStateRestoreOperator[] = "Q";
const char kStateSaveOperator[] = "q";
const char kStrokeOperator[] = "S";
const char kTextBeginOperator[] = "BT";
const char kTextEndOperator[] = "ET";
class AutoClosedCommand {
public:
AutoClosedCommand(fxcrt::ostringstream* stream,
ByteString open,
ByteString close)
: stream_(stream), close_(close) {
*stream_ << open << "\n";
}
virtual ~AutoClosedCommand() { *stream_ << close_ << "\n"; }
private:
UnownedPtr<fxcrt::ostringstream> const stream_;
ByteString close_;
};
class AutoClosedQCommand final : public AutoClosedCommand {
public:
explicit AutoClosedQCommand(fxcrt::ostringstream* stream)
: AutoClosedCommand(stream, kStateSaveOperator, kStateRestoreOperator) {}
~AutoClosedQCommand() override = default;
};
void WriteMove(fxcrt::ostringstream& stream, const CFX_PointF& point) {
WritePoint(stream, point) << " " << kMoveToOperator << "\n";
}
void WriteLine(fxcrt::ostringstream& stream, const CFX_PointF& point) {
WritePoint(stream, point) << " " << kLineToOperator << "\n";
}
void WriteClosedLoop(fxcrt::ostringstream& stream,
pdfium::span<const CFX_PointF> points) {
WriteMove(stream, points[0]);
for (const auto& point : points.subspan(1))
WriteLine(stream, point);
WriteLine(stream, points[0]);
}
void WriteBezierCurve(fxcrt::ostringstream& stream,
const CFX_PointF& point1,
const CFX_PointF& point2,
const CFX_PointF& point3) {
WritePoint(stream, point1) << " ";
WritePoint(stream, point2) << " ";
WritePoint(stream, point3) << " " << kCurveToOperator << "\n";
}
void WriteAppendRect(fxcrt::ostringstream& stream, const CFX_FloatRect& rect) {
WriteRect(stream, rect) << " " << kAppendRectOperator << "\n";
}
ByteString GetStrokeColorAppStream(const CFX_Color& color) {
fxcrt::ostringstream sColorStream;
switch (color.nColorType) {
case CFX_Color::Type::kTransparent:
break;
case CFX_Color::Type::kGray:
sColorStream << color.fColor1 << " " << kSetGrayStrokedOperator << "\n";
break;
case CFX_Color::Type::kRGB:
sColorStream << color.fColor1 << " " << color.fColor2 << " "
<< color.fColor3 << " " << kSetRGBStrokedOperator << "\n";
break;
case CFX_Color::Type::kCMYK:
sColorStream << color.fColor1 << " " << color.fColor2 << " "
<< color.fColor3 << " " << color.fColor4 << " "
<< kSetCMKYStrokedOperator << "\n";
break;
}
return ByteString(sColorStream);
}
ByteString GetFillColorAppStream(const CFX_Color& color) {
fxcrt::ostringstream sColorStream;
switch (color.nColorType) {
case CFX_Color::Type::kTransparent:
break;
case CFX_Color::Type::kGray:
sColorStream << color.fColor1 << " " << kSetGrayOperator << "\n";
break;
case CFX_Color::Type::kRGB:
sColorStream << color.fColor1 << " " << color.fColor2 << " "
<< color.fColor3 << " " << kSetRGBOperator << "\n";
break;
case CFX_Color::Type::kCMYK:
sColorStream << color.fColor1 << " " << color.fColor2 << " "
<< color.fColor3 << " " << color.fColor4 << " "
<< kSetCMYKOperator << "\n";
break;
}
return ByteString(sColorStream);
}
ByteString GetAP_Check(const CFX_FloatRect& crBBox) {
const float fWidth = crBBox.Width();
const float fHeight = crBBox.Height();
CFX_PointF pts[8][3] = {{CFX_PointF(0.28f, 0.52f), CFX_PointF(0.27f, 0.48f),
CFX_PointF(0.29f, 0.40f)},
{CFX_PointF(0.30f, 0.33f), CFX_PointF(0.31f, 0.29f),
CFX_PointF(0.31f, 0.28f)},
{CFX_PointF(0.39f, 0.28f), CFX_PointF(0.49f, 0.29f),
CFX_PointF(0.77f, 0.67f)},
{CFX_PointF(0.76f, 0.68f), CFX_PointF(0.78f, 0.69f),
CFX_PointF(0.76f, 0.75f)},
{CFX_PointF(0.76f, 0.75f), CFX_PointF(0.73f, 0.80f),
CFX_PointF(0.68f, 0.75f)},
{CFX_PointF(0.68f, 0.74f), CFX_PointF(0.68f, 0.74f),
CFX_PointF(0.44f, 0.47f)},
{CFX_PointF(0.43f, 0.47f), CFX_PointF(0.40f, 0.47f),
CFX_PointF(0.41f, 0.58f)},
{CFX_PointF(0.40f, 0.60f), CFX_PointF(0.28f, 0.66f),
CFX_PointF(0.30f, 0.56f)}};
for (size_t i = 0; i < std::size(pts); ++i) {
for (size_t j = 0; j < std::size(pts[0]); ++j) {
pts[i][j].x = pts[i][j].x * fWidth + crBBox.left;
pts[i][j].y *= pts[i][j].y * fHeight + crBBox.bottom;
}
}
fxcrt::ostringstream csAP;
WriteMove(csAP, pts[0][0]);
for (size_t i = 0; i < std::size(pts); ++i) {
size_t nNext = i < std::size(pts) - 1 ? i + 1 : 0;
const CFX_PointF& pt_next = pts[nNext][0];
float px1 = pts[i][1].x - pts[i][0].x;
float py1 = pts[i][1].y - pts[i][0].y;
float px2 = pts[i][2].x - pt_next.x;
float py2 = pts[i][2].y - pt_next.y;
WriteBezierCurve(
csAP,
{pts[i][0].x + px1 * FXSYS_BEZIER, pts[i][0].y + py1 * FXSYS_BEZIER},
{pt_next.x + px2 * FXSYS_BEZIER, pt_next.y + py2 * FXSYS_BEZIER},
pt_next);
}
return ByteString(csAP);
}
ByteString GetAP_Circle(const CFX_FloatRect& crBBox) {
fxcrt::ostringstream csAP;
float fWidth = crBBox.Width();
float fHeight = crBBox.Height();
CFX_PointF pt1(crBBox.left, crBBox.bottom + fHeight / 2);
CFX_PointF pt2(crBBox.left + fWidth / 2, crBBox.top);
CFX_PointF pt3(crBBox.right, crBBox.bottom + fHeight / 2);
CFX_PointF pt4(crBBox.left + fWidth / 2, crBBox.bottom);
WriteMove(csAP, pt1);
float px = pt2.x - pt1.x;
float py = pt2.y - pt1.y;
WriteBezierCurve(csAP, {pt1.x, pt1.y + py * FXSYS_BEZIER},
{pt2.x - px * FXSYS_BEZIER, pt2.y}, pt2);
px = pt3.x - pt2.x;
py = pt2.y - pt3.y;
WriteBezierCurve(csAP, {pt2.x + px * FXSYS_BEZIER, pt2.y},
{pt3.x, pt3.y + py * FXSYS_BEZIER}, pt3);
px = pt3.x - pt4.x;
py = pt3.y - pt4.y;
WriteBezierCurve(csAP, {pt3.x, pt3.y - py * FXSYS_BEZIER},
{pt4.x + px * FXSYS_BEZIER, pt4.y}, pt4);
px = pt4.x - pt1.x;
py = pt1.y - pt4.y;
WriteBezierCurve(csAP, {pt4.x - px * FXSYS_BEZIER, pt4.y},
{pt1.x, pt1.y - py * FXSYS_BEZIER}, pt1);
return ByteString(csAP);
}
ByteString GetAP_Cross(const CFX_FloatRect& crBBox) {
fxcrt::ostringstream csAP;
WriteMove(csAP, {crBBox.left, crBBox.top});
WriteLine(csAP, {crBBox.right, crBBox.bottom});
WriteMove(csAP, {crBBox.left, crBBox.bottom});
WriteLine(csAP, {crBBox.right, crBBox.top});
return ByteString(csAP);
}
ByteString GetAP_Diamond(const CFX_FloatRect& crBBox) {
fxcrt::ostringstream csAP;
float fWidth = crBBox.Width();
float fHeight = crBBox.Height();
const CFX_PointF points[] = {{crBBox.left, crBBox.bottom + fHeight / 2},
{crBBox.left + fWidth / 2, crBBox.top},
{crBBox.right, crBBox.bottom + fHeight / 2},
{crBBox.left + fWidth / 2, crBBox.bottom}};
WriteClosedLoop(csAP, points);
return ByteString(csAP);
}
ByteString GetAP_Square(const CFX_FloatRect& crBBox) {
fxcrt::ostringstream csAP;
const CFX_PointF points[] = {{crBBox.left, crBBox.top},
{crBBox.right, crBBox.top},
{crBBox.right, crBBox.bottom},
{crBBox.left, crBBox.bottom}};
WriteClosedLoop(csAP, points);
return ByteString(csAP);
}
ByteString GetAP_Star(const CFX_FloatRect& crBBox) {
fxcrt::ostringstream csAP;
float fRadius = (crBBox.top - crBBox.bottom) / (1 + cosf(FXSYS_PI / 5.0f));
CFX_PointF ptCenter = CFX_PointF((crBBox.left + crBBox.right) / 2.0f,
(crBBox.top + crBBox.bottom) / 2.0f);
CFX_PointF points[5];
float fAngle = FXSYS_PI / 10.0f;
for (auto& point : points) {
point =
ptCenter + CFX_PointF(fRadius * cosf(fAngle), fRadius * sinf(fAngle));
fAngle += FXSYS_PI * 2 / 5.0f;
}
WriteMove(csAP, points[0]);
int next = 0;
for (size_t i = 0; i < std::size(points); ++i) {
next = (next + 2) % std::size(points);
WriteLine(csAP, points[next]);
}
return ByteString(csAP);
}
ByteString GetAP_HalfCircle(const CFX_FloatRect& crBBox, float fRotate) {
fxcrt::ostringstream csAP;
float fWidth = crBBox.Width();
float fHeight = crBBox.Height();
CFX_PointF pt1(-fWidth / 2, 0);
CFX_PointF pt2(0, fHeight / 2);
CFX_PointF pt3(fWidth / 2, 0);
CFX_Matrix rotate_matrix(cos(fRotate), sin(fRotate), -sin(fRotate),
cos(fRotate), crBBox.left + fWidth / 2,
crBBox.bottom + fHeight / 2);
WriteMatrix(csAP, rotate_matrix) << " " << kConcatMatrixOperator << "\n";
WriteMove(csAP, pt1);
float px = pt2.x - pt1.x;
float py = pt2.y - pt1.y;
WriteBezierCurve(csAP, {pt1.x, pt1.y + py * FXSYS_BEZIER},
{pt2.x - px * FXSYS_BEZIER, pt2.y}, pt2);
px = pt3.x - pt2.x;
py = pt2.y - pt3.y;
WriteBezierCurve(csAP, {pt2.x + px * FXSYS_BEZIER, pt2.y},
{pt3.x, pt3.y + py * FXSYS_BEZIER}, pt3);
return ByteString(csAP);
}
ByteString GetAppStream_Check(const CFX_FloatRect& rcBBox,
const CFX_Color& crText) {
fxcrt::ostringstream sAP;
{
AutoClosedQCommand q(&sAP);
sAP << GetFillColorAppStream(crText) << GetAP_Check(rcBBox) << kFillOperator
<< "\n";
}
return ByteString(sAP);
}
ByteString GetAppStream_Circle(const CFX_FloatRect& rcBBox,
const CFX_Color& crText) {
fxcrt::ostringstream sAP;
{
AutoClosedQCommand q(&sAP);
sAP << GetFillColorAppStream(crText) << GetAP_Circle(rcBBox)
<< kFillOperator << "\n";
}
return ByteString(sAP);
}
ByteString GetAppStream_Cross(const CFX_FloatRect& rcBBox,
const CFX_Color& crText) {
fxcrt::ostringstream sAP;
{
AutoClosedQCommand q(&sAP);
sAP << GetStrokeColorAppStream(crText) << GetAP_Cross(rcBBox)
<< kStrokeOperator << "\n";
}
return ByteString(sAP);
}
ByteString GetAppStream_Diamond(const CFX_FloatRect& rcBBox,
const CFX_Color& crText) {
fxcrt::ostringstream sAP;
{
AutoClosedQCommand q(&sAP);
sAP << "1 " << kSetLineWidthOperator << "\n"
<< GetFillColorAppStream(crText) << GetAP_Diamond(rcBBox)
<< kFillOperator << "\n";
}
return ByteString(sAP);
}
ByteString GetAppStream_Square(const CFX_FloatRect& rcBBox,
const CFX_Color& crText) {
fxcrt::ostringstream sAP;
{
AutoClosedQCommand q(&sAP);
sAP << GetFillColorAppStream(crText) << GetAP_Square(rcBBox)
<< kFillOperator << "\n";
}
return ByteString(sAP);
}
ByteString GetAppStream_Star(const CFX_FloatRect& rcBBox,
const CFX_Color& crText) {
fxcrt::ostringstream sAP;
{
AutoClosedQCommand q(&sAP);
sAP << GetFillColorAppStream(crText) << GetAP_Star(rcBBox) << kFillOperator
<< "\n";
}
return ByteString(sAP);
}
ByteString GetCircleFillAppStream(const CFX_FloatRect& rect,
const CFX_Color& color) {
fxcrt::ostringstream sAppStream;
ByteString sColor = GetFillColorAppStream(color);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q(&sAppStream);
sAppStream << sColor << GetAP_Circle(rect) << kFillOperator << "\n";
}
return ByteString(sAppStream);
}
ByteString GetCircleBorderAppStream(const CFX_FloatRect& rect,
float fWidth,
const CFX_Color& color,
const CFX_Color& crLeftTop,
const CFX_Color& crRightBottom,
BorderStyle nStyle,
const CPWL_Dash& dash) {
fxcrt::ostringstream sAppStream;
ByteString sColor;
if (fWidth > 0.0f) {
AutoClosedQCommand q(&sAppStream);
float fHalfWidth = fWidth / 2.0f;
CFX_FloatRect rect_by_2 = rect.GetDeflated(fHalfWidth, fHalfWidth);
float div = fHalfWidth * 0.75f;
CFX_FloatRect rect_by_75 = rect.GetDeflated(div, div);
switch (nStyle) {
case BorderStyle::kSolid:
case BorderStyle::kUnderline: {
sColor = GetStrokeColorAppStream(color);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q2(&sAppStream);
sAppStream << fWidth << " " << kSetLineWidthOperator << "\n"
<< sColor << GetAP_Circle(rect_by_2) << " "
<< kStrokeOperator << "\n";
}
} break;
case BorderStyle::kDash: {
sColor = GetStrokeColorAppStream(color);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q2(&sAppStream);
sAppStream << fWidth << " " << kSetLineWidthOperator << "\n"
<< "[" << dash.nDash << " " << dash.nGap << "] "
<< dash.nPhase << " " << kSetDashOperator << "\n"
<< sColor << GetAP_Circle(rect_by_2) << " "
<< kStrokeOperator << "\n";
}
} break;
case BorderStyle::kBeveled: {
sColor = GetStrokeColorAppStream(color);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q2(&sAppStream);
sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
<< sColor << GetAP_Circle(rect) << " " << kStrokeOperator
<< "\n";
}
sColor = GetStrokeColorAppStream(crLeftTop);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q2(&sAppStream);
sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
<< sColor << GetAP_HalfCircle(rect_by_75, FXSYS_PI / 4.0f)
<< " " << kStrokeOperator << "\n";
}
sColor = GetStrokeColorAppStream(crRightBottom);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q2(&sAppStream);
sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
<< sColor
<< GetAP_HalfCircle(rect_by_75, FXSYS_PI * 5 / 4.0f) << " "
<< kStrokeOperator << "\n";
}
} break;
case BorderStyle::kInset: {
sColor = GetStrokeColorAppStream(color);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q2(&sAppStream);
sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
<< sColor << GetAP_Circle(rect) << " " << kStrokeOperator
<< "\n";
}
sColor = GetStrokeColorAppStream(crLeftTop);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q2(&sAppStream);
sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
<< sColor << GetAP_HalfCircle(rect_by_75, FXSYS_PI / 4.0f)
<< " " << kStrokeOperator << "\n";
}
sColor = GetStrokeColorAppStream(crRightBottom);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q2(&sAppStream);
sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
<< sColor
<< GetAP_HalfCircle(rect_by_75, FXSYS_PI * 5 / 4.0f) << " "
<< kStrokeOperator << "\n";
}
} break;
}
}
return ByteString(sAppStream);
}
ByteString GetCheckBoxAppStream(const CFX_FloatRect& rcBBox,
CheckStyle nStyle,
const CFX_Color& crText) {
CFX_FloatRect rcCenter = rcBBox.GetCenterSquare();
switch (nStyle) {
case CheckStyle::kCheck:
return GetAppStream_Check(rcCenter, crText);
case CheckStyle::kCircle:
rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
return GetAppStream_Circle(rcCenter, crText);
case CheckStyle::kCross:
return GetAppStream_Cross(rcCenter, crText);
case CheckStyle::kDiamond:
rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
return GetAppStream_Diamond(rcCenter, crText);
case CheckStyle::kSquare:
rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
return GetAppStream_Square(rcCenter, crText);
case CheckStyle::kStar:
rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
return GetAppStream_Star(rcCenter, crText);
}
}
ByteString GetRadioButtonAppStream(const CFX_FloatRect& rcBBox,
CheckStyle nStyle,
const CFX_Color& crText) {
CFX_FloatRect rcCenter = rcBBox.GetCenterSquare();
switch (nStyle) {
case CheckStyle::kCheck:
return GetAppStream_Check(rcCenter, crText);
case CheckStyle::kCircle:
rcCenter.ScaleFromCenterPoint(1.0f / 2.0f);
return GetAppStream_Circle(rcCenter, crText);
case CheckStyle::kCross:
return GetAppStream_Cross(rcCenter, crText);
case CheckStyle::kDiamond:
rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
return GetAppStream_Diamond(rcCenter, crText);
case CheckStyle::kSquare:
rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
return GetAppStream_Square(rcCenter, crText);
case CheckStyle::kStar:
rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
return GetAppStream_Star(rcCenter, crText);
}
}
ByteString GetFontSetString(IPVT_FontMap* pFontMap,
int32_t nFontIndex,
float fFontSize) {
if (!pFontMap)
return ByteString();
ByteString sFontAlias = pFontMap->GetPDFFontAlias(nFontIndex);
if (sFontAlias.GetLength() <= 0 || fFontSize <= 0)
return ByteString();
fxcrt::ostringstream sRet;
sRet << "/" << sFontAlias << " " << fFontSize << " "
<< kSetTextFontAndSizeOperator << "\n";
return ByteString(sRet);
}
ByteString GetWordRenderString(ByteStringView strWords) {
if (strWords.IsEmpty())
return ByteString();
return PDF_EncodeString(strWords) + " " + kShowTextOperator + "\n";
}
ByteString GetEditAppStream(CPWL_EditImpl* pEdit,
const CFX_PointF& ptOffset,
bool bContinuous,
uint16_t SubWord) {
CPWL_EditImpl::Iterator* pIterator = pEdit->GetIterator();
pIterator->SetAt(0);
fxcrt::ostringstream sEditStream;
int32_t nCurFontIndex = -1;
CFX_PointF ptOld;
CFX_PointF ptNew;
CPVT_WordPlace oldplace;
ByteString sWords;
while (pIterator->NextWord()) {
CPVT_WordPlace place = pIterator->GetAt();
if (bContinuous) {
if (place.LineCmp(oldplace) != 0) {
if (!sWords.IsEmpty()) {
sEditStream << GetWordRenderString(sWords.AsStringView());
sWords.clear();
}
CPVT_Word word;
if (pIterator->GetWord(word)) {
ptNew = CFX_PointF(word.ptWord.x + ptOffset.x,
word.ptWord.y + ptOffset.y);
} else {
CPVT_Line line;
pIterator->GetLine(line);
ptNew = CFX_PointF(line.ptLine.x + ptOffset.x,
line.ptLine.y + ptOffset.y);
}
if (ptNew.x != ptOld.x || ptNew.y != ptOld.y) {
WritePoint(sEditStream, {ptNew.x - ptOld.x, ptNew.y - ptOld.y})
<< " " << kMoveTextPositionOperator << "\n";
ptOld = ptNew;
}
}
CPVT_Word word;
if (pIterator->GetWord(word)) {
if (word.nFontIndex != nCurFontIndex) {
if (!sWords.IsEmpty()) {
sEditStream << GetWordRenderString(sWords.AsStringView());
sWords.clear();
}
sEditStream << GetFontSetString(pEdit->GetFontMap(), word.nFontIndex,
word.fFontSize);
nCurFontIndex = word.nFontIndex;
}
sWords += pEdit->GetPDFWordString(nCurFontIndex, word.Word, SubWord);
}
oldplace = place;
} else {
CPVT_Word word;
if (pIterator->GetWord(word)) {
ptNew =
CFX_PointF(word.ptWord.x + ptOffset.x, word.ptWord.y + ptOffset.y);
if (ptNew.x != ptOld.x || ptNew.y != ptOld.y) {
WritePoint(sEditStream, {ptNew.x - ptOld.x, ptNew.y - ptOld.y})
<< " " << kMoveTextPositionOperator << "\n";
ptOld = ptNew;
}
if (word.nFontIndex != nCurFontIndex) {
sEditStream << GetFontSetString(pEdit->GetFontMap(), word.nFontIndex,
word.fFontSize);
nCurFontIndex = word.nFontIndex;
}
sEditStream << GetWordRenderString(
pEdit->GetPDFWordString(nCurFontIndex, word.Word, SubWord)
.AsStringView());
}
}
}
if (!sWords.IsEmpty())
sEditStream << GetWordRenderString(sWords.AsStringView());
fxcrt::ostringstream sAppStream;
if (sEditStream.tellp() > 0) {
sAppStream << sEditStream.str();
}
return ByteString(sAppStream);
}
ByteString GenerateIconAppStream(CPDF_IconFit& fit,
RetainPtr<CPDF_Stream> pIconStream,
const CFX_FloatRect& rcIcon) {
if (rcIcon.IsEmpty() || !pIconStream)
return ByteString();
CPWL_Wnd::CreateParams cp(nullptr, nullptr, nullptr);
cp.dwFlags = PWS_VISIBLE;
auto pWnd = std::make_unique<CPWL_Wnd>(cp, nullptr);
pWnd->Realize();
if (!pWnd->Move(rcIcon, false, false))
return ByteString();
auto pPDFIcon = std::make_unique<CPDF_Icon>(std::move(pIconStream));
ByteString sAlias = pPDFIcon->GetImageAlias();
if (sAlias.GetLength() <= 0)
return ByteString();
const CFX_FloatRect rcPlate = pWnd->GetClientRect();
const CFX_SizeF image_size = pPDFIcon->GetImageSize();
const CFX_Matrix mt = pPDFIcon->GetImageMatrix().GetInverse();
const CFX_VectorF scale = fit.GetScale(image_size, rcPlate);
const CFX_VectorF offset = fit.GetImageOffset(image_size, scale, rcPlate);
fxcrt::ostringstream str;
{
AutoClosedQCommand q(&str);
WriteAppendRect(str, rcPlate);
str << kSetNonZeroWindingClipOperator << " "
<< kEndPathNoFillOrStrokeOperator << "\n";
CFX_Matrix scale_matrix(scale.x, 0, 0, scale.y, rcPlate.left + offset.x,
rcPlate.bottom + offset.y);
WriteMatrix(str, scale_matrix) << " " << kConcatMatrixOperator << "\n";
WriteMatrix(str, mt) << " " << kConcatMatrixOperator << "\n";
str << "0 " << kSetGrayOperator << " 0 " << kSetGrayStrokedOperator << " 1 "
<< kSetLineWidthOperator << " /" << sAlias << " "
<< kInvokeNamedXObjectOperator << "\n";
}
pWnd->Destroy();
return ByteString(str);
}
ByteString GetPushButtonAppStream(const CFX_FloatRect& rcBBox,
IPVT_FontMap* pFontMap,
RetainPtr<CPDF_Stream> pIconStream,
CPDF_IconFit& IconFit,
const WideString& sLabel,
const CFX_Color& crText,
float fFontSize,
ButtonStyle nLayOut) {
const float fAutoFontScale = 1.0f / 3.0f;
auto pEdit = std::make_unique<CPWL_EditImpl>();
pEdit->SetFontMap(pFontMap);
pEdit->SetAlignmentH(1);
pEdit->SetAlignmentV(1);
pEdit->SetMultiLine(false);
pEdit->SetAutoReturn(false);
if (FXSYS_IsFloatZero(fFontSize))
pEdit->SetAutoFontSize(true);
else
pEdit->SetFontSize(fFontSize);
pEdit->Initialize();
pEdit->SetText(sLabel);
pEdit->Paint();
CFX_FloatRect rcLabelContent = pEdit->GetContentRect();
CFX_FloatRect rcLabel;
CFX_FloatRect rcIcon;
float fWidth = 0.0f;
float fHeight = 0.0f;
switch (nLayOut) {
case ButtonStyle::kLabel:
rcLabel = rcBBox;
break;
case ButtonStyle::kIcon:
rcIcon = rcBBox;
break;
case ButtonStyle::kIconTopLabelBottom:
if (pIconStream) {
if (FXSYS_IsFloatZero(fFontSize)) {
fHeight = rcBBox.Height();
rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcBBox.right,
rcBBox.bottom + fHeight * fAutoFontScale);
rcIcon =
CFX_FloatRect(rcBBox.left, rcLabel.top, rcBBox.right, rcBBox.top);
} else {
fHeight = rcLabelContent.Height();
if (rcBBox.bottom + fHeight > rcBBox.top) {
rcLabel = rcBBox;
} else {
rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcBBox.right,
rcBBox.bottom + fHeight);
rcIcon = CFX_FloatRect(rcBBox.left, rcLabel.top, rcBBox.right,
rcBBox.top);
}
}
} else {
rcLabel = rcBBox;
}
break;
case ButtonStyle::kIconBottomLabelTop:
if (pIconStream) {
if (FXSYS_IsFloatZero(fFontSize)) {
fHeight = rcBBox.Height();
rcLabel =
CFX_FloatRect(rcBBox.left, rcBBox.top - fHeight * fAutoFontScale,
rcBBox.right, rcBBox.top);
rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcBBox.right,
rcLabel.bottom);
} else {
fHeight = rcLabelContent.Height();
if (rcBBox.bottom + fHeight > rcBBox.top) {
rcLabel = rcBBox;
} else {
rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.top - fHeight,
rcBBox.right, rcBBox.top);
rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcBBox.right,
rcLabel.bottom);
}
}
} else {
rcLabel = rcBBox;
}
break;
case ButtonStyle::kIconLeftLabelRight:
if (pIconStream) {
if (FXSYS_IsFloatZero(fFontSize)) {
fWidth = rcBBox.right - rcBBox.left;
if (rcLabelContent.Width() < fWidth * fAutoFontScale) {
rcLabel = CFX_FloatRect(rcBBox.right - fWidth * fAutoFontScale,
rcBBox.bottom, rcBBox.right, rcBBox.top);
rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcLabel.left,
rcBBox.top);
} else {
if (rcLabelContent.Width() < fWidth) {
rcLabel = CFX_FloatRect(rcBBox.right - rcLabelContent.Width(),
rcBBox.bottom, rcBBox.right, rcBBox.top);
rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcLabel.left,
rcBBox.top);
} else {
rcLabel = rcBBox;
}
}
} else {
fWidth = rcLabelContent.Width();
if (rcBBox.left + fWidth > rcBBox.right) {
rcLabel = rcBBox;
} else {
rcLabel = CFX_FloatRect(rcBBox.right - fWidth, rcBBox.bottom,
rcBBox.right, rcBBox.top);
rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcLabel.left,
rcBBox.top);
}
}
} else {
rcLabel = rcBBox;
}
break;
case ButtonStyle::kIconRightLabelLeft:
if (pIconStream) {
if (FXSYS_IsFloatZero(fFontSize)) {
fWidth = rcBBox.right - rcBBox.left;
if (rcLabelContent.Width() < fWidth * fAutoFontScale) {
rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom,
rcBBox.left + fWidth * fAutoFontScale,
rcBBox.top);
rcIcon = CFX_FloatRect(rcLabel.right, rcBBox.bottom, rcBBox.right,
rcBBox.top);
} else {
if (rcLabelContent.Width() < fWidth) {
rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom,
rcBBox.left + rcLabelContent.Width(),
rcBBox.top);
rcIcon = CFX_FloatRect(rcLabel.right, rcBBox.bottom, rcBBox.right,
rcBBox.top);
} else {
rcLabel = rcBBox;
}
}
} else {
fWidth = rcLabelContent.Width();
if (rcBBox.left + fWidth > rcBBox.right) {
rcLabel = rcBBox;
} else {
rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom,
rcBBox.left + fWidth, rcBBox.top);
rcIcon = CFX_FloatRect(rcLabel.right, rcBBox.bottom, rcBBox.right,
rcBBox.top);
}
}
} else {
rcLabel = rcBBox;
}
break;
case ButtonStyle::kLabelOverIcon:
rcLabel = rcBBox;
rcIcon = rcBBox;
break;
}
fxcrt::ostringstream sTemp;
sTemp << GenerateIconAppStream(IconFit, std::move(pIconStream), rcIcon);
if (!rcLabel.IsEmpty()) {
pEdit->SetPlateRect(rcLabel);
pEdit->Paint();
ByteString sEdit =
GetEditAppStream(pEdit.get(), CFX_PointF(0.0f, 0.0f), true, 0);
if (sEdit.GetLength() > 0) {
AutoClosedCommand bt(&sTemp, kTextBeginOperator, kTextEndOperator);
sTemp << GetFillColorAppStream(crText) << sEdit;
}
}
if (sTemp.tellp() <= 0)
return ByteString();
fxcrt::ostringstream sAppStream;
{
AutoClosedQCommand q(&sAppStream);
WriteAppendRect(sAppStream, rcBBox);
sAppStream << kSetNonZeroWindingClipOperator << " "
<< kEndPathNoFillOrStrokeOperator << "\n";
sAppStream << sTemp.str().c_str();
}
return ByteString(sAppStream);
}
ByteString GetBorderAppStreamInternal(const CFX_FloatRect& rect,
float fWidth,
const CFX_Color& color,
const CFX_Color& crLeftTop,
const CFX_Color& crRightBottom,
BorderStyle nStyle,
const CPWL_Dash& dash) {
fxcrt::ostringstream sAppStream;
ByteString sColor;
float fLeft = rect.left;
float fRight = rect.right;
float fTop = rect.top;
float fBottom = rect.bottom;
if (fWidth > 0.0f) {
float fHalfWidth = fWidth / 2.0f;
AutoClosedQCommand q(&sAppStream);
switch (nStyle) {
case BorderStyle::kSolid:
sColor = GetFillColorAppStream(color);
if (sColor.GetLength() > 0) {
sAppStream << sColor;
WriteAppendRect(sAppStream, {fLeft, fBottom, fRight, fTop});
WriteAppendRect(sAppStream, {fLeft + fWidth, fBottom + fWidth,
fRight - fWidth, fTop - fWidth});
sAppStream << kFillEvenOddOperator << "\n";
}
break;
case BorderStyle::kDash:
sColor = GetStrokeColorAppStream(color);
if (sColor.GetLength() > 0) {
sAppStream << sColor;
sAppStream << fWidth << " " << kSetLineWidthOperator << " ["
<< dash.nDash << " " << dash.nGap << "] " << dash.nPhase
<< " " << kSetDashOperator << "\n";
const CFX_PointF points[] = {
{fLeft + fWidth / 2, fBottom + fWidth / 2},
{fLeft + fWidth / 2, fTop - fWidth / 2},
{fRight - fWidth / 2, fTop - fWidth / 2},
{fRight - fWidth / 2, fBottom + fWidth / 2}};
WriteClosedLoop(sAppStream, points);
sAppStream << kStrokeOperator << "\n";
}
break;
case BorderStyle::kBeveled:
case BorderStyle::kInset:
sColor = GetFillColorAppStream(crLeftTop);
if (sColor.GetLength() > 0) {
sAppStream << sColor;
WriteMove(sAppStream, {fLeft + fHalfWidth, fBottom + fHalfWidth});
WriteLine(sAppStream, {fLeft + fHalfWidth, fTop - fHalfWidth});
WriteLine(sAppStream, {fRight - fHalfWidth, fTop - fHalfWidth});
WriteLine(sAppStream,
{fRight - fHalfWidth * 2, fTop - fHalfWidth * 2});
WriteLine(sAppStream,
{fLeft + fHalfWidth * 2, fTop - fHalfWidth * 2});
WriteLine(sAppStream,
{fLeft + fHalfWidth * 2, fBottom + fHalfWidth * 2});
sAppStream << kFillOperator << "\n";
}
sColor = GetFillColorAppStream(crRightBottom);
if (sColor.GetLength() > 0) {
sAppStream << sColor;
WriteMove(sAppStream, {fRight - fHalfWidth, fTop - fHalfWidth});
WriteLine(sAppStream, {fRight - fHalfWidth, fBottom + fHalfWidth});
WriteLine(sAppStream, {fLeft + fHalfWidth, fBottom + fHalfWidth});
WriteLine(sAppStream,
{fLeft + fHalfWidth * 2, fBottom + fHalfWidth * 2});
WriteLine(sAppStream,
{fRight - fHalfWidth * 2, fBottom + fHalfWidth * 2});
WriteLine(sAppStream,
{fRight - fHalfWidth * 2, fTop - fHalfWidth * 2});
sAppStream << kFillOperator << "\n";
}
sColor = GetFillColorAppStream(color);
if (sColor.GetLength() > 0) {
sAppStream << sColor;
WriteAppendRect(sAppStream, {fLeft, fBottom, fRight, fTop});
WriteAppendRect(sAppStream, {fLeft + fHalfWidth, fBottom + fHalfWidth,
fRight - fHalfWidth, fTop - fHalfWidth});
sAppStream << kFillEvenOddOperator << "\n";
}
break;
case BorderStyle::kUnderline:
sColor = GetStrokeColorAppStream(color);
if (sColor.GetLength() > 0) {
sAppStream << sColor;
sAppStream << fWidth << " " << kSetLineWidthOperator << "\n";
WriteMove(sAppStream, {fLeft, fBottom + fWidth / 2});
WriteLine(sAppStream, {fRight, fBottom + fWidth / 2});
sAppStream << kStrokeOperator << "\n";
}
break;
}
}
return ByteString(sAppStream);
}
ByteString GetDropButtonAppStream(const CFX_FloatRect& rcBBox) {
if (rcBBox.IsEmpty())
return ByteString();
fxcrt::ostringstream sAppStream;
{
AutoClosedQCommand q(&sAppStream);
sAppStream << GetFillColorAppStream(
CFX_Color(CFX_Color::Type::kRGB, 220.0f / 255.0f, 220.0f / 255.0f,
220.0f / 255.0f));
WriteAppendRect(sAppStream, rcBBox);
sAppStream << kFillOperator << "\n";
}
{
AutoClosedQCommand q(&sAppStream);
sAppStream << GetBorderAppStreamInternal(
rcBBox, 2, CFX_Color(CFX_Color::Type::kGray, 0),
CFX_Color(CFX_Color::Type::kGray, 1),
CFX_Color(CFX_Color::Type::kGray, 0.5), BorderStyle::kBeveled,
CPWL_Dash(3, 0, 0));
}
CFX_PointF ptCenter = CFX_PointF((rcBBox.left + rcBBox.right) / 2,
(rcBBox.top + rcBBox.bottom) / 2);
if (FXSYS_IsFloatBigger(rcBBox.right - rcBBox.left, 6) &&
FXSYS_IsFloatBigger(rcBBox.top - rcBBox.bottom, 6)) {
AutoClosedQCommand q(&sAppStream);
const CFX_PointF points[] = {{ptCenter.x - 3, ptCenter.y + 1.5f},
{ptCenter.x + 3, ptCenter.y + 1.5f},
{ptCenter.x, ptCenter.y - 1.5f}};
sAppStream << " 0 " << kSetGrayOperator << "\n";
WriteClosedLoop(sAppStream, points);
sAppStream << kFillOperator << "\n";
}
return ByteString(sAppStream);
}
ByteString GetRectFillAppStream(const CFX_FloatRect& rect,
const CFX_Color& color) {
fxcrt::ostringstream sAppStream;
ByteString sColor = GetFillColorAppStream(color);
if (sColor.GetLength() > 0) {
AutoClosedQCommand q(&sAppStream);
sAppStream << sColor;
WriteAppendRect(sAppStream, rect);
sAppStream << kFillOperator << "\n";
}
return ByteString(sAppStream);
}
void SetDefaultIconName(CPDF_Stream* pIcon, const char* name) {
if (!pIcon)
return;
RetainPtr<CPDF_Dictionary> pImageDict = pIcon->GetMutableDict();
if (!pImageDict)
return;
if (pImageDict->KeyExist("Name"))
return;
pImageDict->SetNewFor<CPDF_String>("Name", name, false);
}
absl::optional<CheckStyle> CheckStyleFromCaption(const WideString& caption) {
if (caption.IsEmpty())
return absl::nullopt;
// Character values are ZapfDingbats encodings of named glyphs.
switch (caption[0]) {
case L'4':
return CheckStyle::kCheck;
case L'8':
return CheckStyle::kCross;
case L'H':
return CheckStyle::kStar;
case L'l':
return CheckStyle::kCircle;
case L'n':
return CheckStyle::kSquare;
case L'u':
return CheckStyle::kDiamond;
default:
return absl::nullopt;
}
}
} // namespace
CPDFSDK_AppStream::CPDFSDK_AppStream(CPDFSDK_Widget* widget,
CPDF_Dictionary* dict)
: widget_(widget), dict_(dict) {}
CPDFSDK_AppStream::~CPDFSDK_AppStream() = default;
void CPDFSDK_AppStream::SetAsPushButton() {
CPDF_FormControl* pControl = widget_->GetFormControl();
CFX_FloatRect rcWindow = widget_->GetRotatedRect();
ButtonStyle nLayout = ButtonStyle::kLabel;
switch (pControl->GetTextPosition()) {
case TEXTPOS_ICON:
nLayout = ButtonStyle::kIcon;
break;
case TEXTPOS_BELOW:
nLayout = ButtonStyle::kIconTopLabelBottom;
break;
case TEXTPOS_ABOVE:
nLayout = ButtonStyle::kIconBottomLabelTop;
break;
case TEXTPOS_RIGHT:
nLayout = ButtonStyle::kIconLeftLabelRight;
break;
case TEXTPOS_LEFT:
nLayout = ButtonStyle::kIconRightLabelLeft;
break;
case TEXTPOS_OVERLAID:
nLayout = ButtonStyle::kLabelOverIcon;
break;
default:
nLayout = ButtonStyle::kLabel;
break;
}
CFX_Color crBackground = pControl->GetOriginalBackgroundColor();
CFX_Color crBorder = pControl->GetOriginalBorderColor();
float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
CPWL_Dash dsBorder(3, 0, 0);
CFX_Color crLeftTop;
CFX_Color crRightBottom;
BorderStyle nBorderStyle = widget_->GetBorderStyle();
switch (nBorderStyle) {
case BorderStyle::kDash:
dsBorder = CPWL_Dash(3, 3, 0);
break;
case BorderStyle::kBeveled:
fBorderWidth *= 2;
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
crRightBottom = crBackground / 2.0f;
break;
case BorderStyle::kInset:
fBorderWidth *= 2;
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
break;
default:
break;
}
CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
CPDF_DefaultAppearance da = pControl->GetDefaultAppearance();
absl::optional<CFX_Color> color = da.GetColor();
CFX_Color crText = color.value_or(CFX_Color(CFX_Color::Type::kGray, 0));
float fFontSize;
ByteString csNameTag;
absl::optional<ByteString> font = da.GetFont(&fFontSize);
if (font.has_value())
csNameTag = font.value();
else
fFontSize = 12.0f;
WideString csWCaption;
WideString csNormalCaption;
WideString csRolloverCaption;
WideString csDownCaption;
if (pControl->HasMKEntry(pdfium::appearance::kCA))
csNormalCaption = pControl->GetNormalCaption();
if (pControl->HasMKEntry(pdfium::appearance::kRC))
csRolloverCaption = pControl->GetRolloverCaption();
if (pControl->HasMKEntry(pdfium::appearance::kAC))
csDownCaption = pControl->GetDownCaption();
RetainPtr<CPDF_Stream> pNormalIcon;
RetainPtr<CPDF_Stream> pRolloverIcon;
RetainPtr<CPDF_Stream> pDownIcon;
if (pControl->HasMKEntry(pdfium::appearance::kI))
pNormalIcon = pControl->GetNormalIcon();
if (pControl->HasMKEntry(pdfium::appearance::kRI))
pRolloverIcon = pControl->GetRolloverIcon();
if (pControl->HasMKEntry(pdfium::appearance::kIX))
pDownIcon = pControl->GetDownIcon();
SetDefaultIconName(pNormalIcon.Get(), "ImgA");
SetDefaultIconName(pRolloverIcon.Get(), "ImgB");
SetDefaultIconName(pDownIcon.Get(), "ImgC");
CPDF_IconFit iconFit = pControl->GetIconFit();
{
CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
widget_->GetPDFAnnot()->GetMutableAnnotDict(), "N");
ByteString csAP =
GetRectFillAppStream(rcWindow, crBackground) +
GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder) +
GetPushButtonAppStream(iconFit.GetFittingBounds() ? rcWindow : rcClient,
&font_map, pNormalIcon, iconFit, csNormalCaption,
crText, fFontSize, nLayout);
Write("N", csAP, ByteString());
if (pNormalIcon)
AddImage("N", pNormalIcon.Get());
CPDF_FormControl::HighlightingMode eHLM = pControl->GetHighlightingMode();
if (eHLM != CPDF_FormControl::kPush && eHLM != CPDF_FormControl::kToggle) {
Remove("D");
Remove("R");
return;
}
if (csRolloverCaption.IsEmpty() && !pRolloverIcon) {
csRolloverCaption = csNormalCaption;
pRolloverIcon = pNormalIcon;
}
}
{
CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
widget_->GetPDFAnnot()->GetMutableAnnotDict(), "R");
ByteString csAP =
GetRectFillAppStream(rcWindow, crBackground) +
GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder) +
GetPushButtonAppStream(iconFit.GetFittingBounds() ? rcWindow : rcClient,
&font_map, pRolloverIcon, iconFit,
csRolloverCaption, crText, fFontSize, nLayout);
Write("R", csAP, ByteString());
if (pRolloverIcon)
AddImage("R", pRolloverIcon.Get());
if (csDownCaption.IsEmpty() && !pDownIcon) {
csDownCaption = csNormalCaption;
pDownIcon = pNormalIcon;
}
switch (nBorderStyle) {
case BorderStyle::kBeveled: {
CFX_Color crTemp = crLeftTop;
crLeftTop = crRightBottom;
crRightBottom = crTemp;
break;
}
case BorderStyle::kInset: {
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
break;
}
default:
break;
}
}
{
CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
widget_->GetPDFAnnot()->GetMutableAnnotDict(), "D");
ByteString csAP =
GetRectFillAppStream(rcWindow, crBackground - 0.25f) +
GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder) +
GetPushButtonAppStream(iconFit.GetFittingBounds() ? rcWindow : rcClient,
&font_map, pDownIcon, iconFit, csDownCaption,
crText, fFontSize, nLayout);
Write("D", csAP, ByteString());
if (pDownIcon)
AddImage("D", pDownIcon.Get());
}
}
void CPDFSDK_AppStream::SetAsCheckBox() {
CPDF_FormControl* pControl = widget_->GetFormControl();
CFX_Color crBackground = pControl->GetOriginalBackgroundColor();
CFX_Color crBorder = pControl->GetOriginalBorderColor();
float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
CPWL_Dash dsBorder(3, 0, 0);
CFX_Color crLeftTop;
CFX_Color crRightBottom;
BorderStyle nBorderStyle = widget_->GetBorderStyle();
switch (nBorderStyle) {
case BorderStyle::kDash:
dsBorder = CPWL_Dash(3, 3, 0);
break;
case BorderStyle::kBeveled:
fBorderWidth *= 2;
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
crRightBottom = crBackground / 2.0f;
break;
case BorderStyle::kInset:
fBorderWidth *= 2;
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
break;
default:
break;
}
CFX_FloatRect rcWindow = widget_->GetRotatedRect();
CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
absl::optional<CFX_Color> color = pControl->GetDefaultAppearance().GetColor();
CFX_Color crText = color.value_or(CFX_Color());
CheckStyle nStyle = CheckStyleFromCaption(pControl->GetNormalCaption())
.value_or(CheckStyle::kCheck);
ByteString csAP_N_ON =
GetRectFillAppStream(rcWindow, crBackground) +
GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder);
ByteString csAP_N_OFF = csAP_N_ON;
switch (nBorderStyle) {
case BorderStyle::kBeveled: {
CFX_Color crTemp = crLeftTop;
crLeftTop = crRightBottom;
crRightBottom = crTemp;
break;
}
case BorderStyle::kInset: {
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
break;
}
default:
break;
}
ByteString csAP_D_ON =
GetRectFillAppStream(rcWindow, crBackground - 0.25f) +
GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder);
ByteString csAP_D_OFF = csAP_D_ON;
csAP_N_ON += GetCheckBoxAppStream(rcClient, nStyle, crText);
csAP_D_ON += GetCheckBoxAppStream(rcClient, nStyle, crText);
Write("N", csAP_N_ON, pControl->GetCheckedAPState());
Write("N", csAP_N_OFF, "Off");
Write("D", csAP_D_ON, pControl->GetCheckedAPState());
Write("D", csAP_D_OFF, "Off");
ByteString csAS = widget_->GetAppState();
if (csAS.IsEmpty())
widget_->SetAppStateOff();
}
void CPDFSDK_AppStream::SetAsRadioButton() {
CPDF_FormControl* pControl = widget_->GetFormControl();
CFX_Color crBackground = pControl->GetOriginalBackgroundColor();
CFX_Color crBorder = pControl->GetOriginalBorderColor();
float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
CPWL_Dash dsBorder(3, 0, 0);
CFX_Color crLeftTop;
CFX_Color crRightBottom;
BorderStyle nBorderStyle = widget_->GetBorderStyle();
switch (nBorderStyle) {
case BorderStyle::kDash:
dsBorder = CPWL_Dash(3, 3, 0);
break;
case BorderStyle::kBeveled:
fBorderWidth *= 2;
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
crRightBottom = crBackground / 2.0f;
break;
case BorderStyle::kInset:
fBorderWidth *= 2;
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
break;
default:
break;
}
CFX_FloatRect rcWindow = widget_->GetRotatedRect();
CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
absl::optional<CFX_Color> color = pControl->GetDefaultAppearance().GetColor();
CFX_Color crText = color.value_or(CFX_Color());
CheckStyle nStyle = CheckStyleFromCaption(pControl->GetNormalCaption())
.value_or(CheckStyle::kCircle);
ByteString csAP_N_ON;
CFX_FloatRect rcCenter = rcWindow.GetCenterSquare().GetDeflated(1.0f, 1.0f);
if (nStyle == CheckStyle::kCircle) {
if (nBorderStyle == BorderStyle::kBeveled) {
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
crRightBottom = crBackground - 0.25f;
} else if (nBorderStyle == BorderStyle::kInset) {
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5f);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75f);
}
csAP_N_ON =
GetCircleFillAppStream(rcCenter, crBackground) +
GetCircleBorderAppStream(rcCenter, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder);
} else {
csAP_N_ON =
GetRectFillAppStream(rcWindow, crBackground) +
GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder);
}
ByteString csAP_N_OFF = csAP_N_ON;
switch (nBorderStyle) {
case BorderStyle::kBeveled: {
CFX_Color crTemp = crLeftTop;
crLeftTop = crRightBottom;
crRightBottom = crTemp;
break;
}
case BorderStyle::kInset: {
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
break;
}
default:
break;
}
ByteString csAP_D_ON;
if (nStyle == CheckStyle::kCircle) {
CFX_Color crBK = crBackground - 0.25f;
if (nBorderStyle == BorderStyle::kBeveled) {
crLeftTop = crBackground - 0.25f;
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
crBK = crBackground;
} else if (nBorderStyle == BorderStyle::kInset) {
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
}
csAP_D_ON =
GetCircleFillAppStream(rcCenter, crBK) +
GetCircleBorderAppStream(rcCenter, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder);
} else {
csAP_D_ON =
GetRectFillAppStream(rcWindow, crBackground - 0.25f) +
GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder);
}
ByteString csAP_D_OFF = csAP_D_ON;
ByteString app_stream = GetRadioButtonAppStream(rcClient, nStyle, crText);
csAP_N_ON += app_stream;
csAP_D_ON += app_stream;
Write("N", csAP_N_ON, pControl->GetCheckedAPState());
Write("N", csAP_N_OFF, "Off");
Write("D", csAP_D_ON, pControl->GetCheckedAPState());
Write("D", csAP_D_OFF, "Off");
ByteString csAS = widget_->GetAppState();
if (csAS.IsEmpty())
widget_->SetAppStateOff();
}
void CPDFSDK_AppStream::SetAsComboBox(absl::optional<WideString> sValue) {
CPDF_FormControl* pControl = widget_->GetFormControl();
CPDF_FormField* pField = pControl->GetField();
fxcrt::ostringstream sBody;
CFX_FloatRect rcClient = widget_->GetClientRect();
CFX_FloatRect rcButton = rcClient;
rcButton.left = rcButton.right - 13;
rcButton.Normalize();
// Font map must outlive |pEdit|.
CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
widget_->GetPDFAnnot()->GetMutableAnnotDict(), "N");
auto pEdit = std::make_unique<CPWL_EditImpl>();
pEdit->EnableRefresh(false);
pEdit->SetFontMap(&font_map);
CFX_FloatRect rcEdit = rcClient;
rcEdit.right = rcButton.left;
rcEdit.Normalize();
pEdit->SetPlateRect(rcEdit);
pEdit->SetAlignmentV(1);
float fFontSize = widget_->GetFontSize();
if (FXSYS_IsFloatZero(fFontSize))
pEdit->SetAutoFontSize(true);
else
pEdit->SetFontSize(fFontSize);
pEdit->Initialize();
if (sValue.has_value()) {
pEdit->SetText(sValue.value());
} else {
int32_t nCurSel = pField->GetSelectedIndex(0);
if (nCurSel < 0) {
pEdit->SetText(pField->GetValue());
} else {
pEdit->SetText(pField->GetOptionLabel(nCurSel));
}
}
pEdit->Paint();
CFX_FloatRect rcContent = pEdit->GetContentRect();
ByteString sEdit = GetEditAppStream(pEdit.get(), CFX_PointF(), true, 0);
if (sEdit.GetLength() > 0) {
sBody << "/Tx ";
AutoClosedCommand bmc(&sBody, kMarkedSequenceBeginOperator,
kMarkedSequenceEndOperator);
AutoClosedQCommand q(&sBody);
if (rcContent.Width() > rcEdit.Width() ||
rcContent.Height() > rcEdit.Height()) {
WriteAppendRect(sBody, rcEdit);
sBody << kSetNonZeroWindingClipOperator << "\n"
<< kEndPathNoFillOrStrokeOperator << "\n";
}
CFX_Color crText = widget_->GetTextPWLColor();
AutoClosedCommand bt(&sBody, kTextBeginOperator, kTextEndOperator);
sBody << GetFillColorAppStream(crText) << sEdit;
}
sBody << GetDropButtonAppStream(rcButton);
Write("N",
GetBackgroundAppStream() + GetBorderAppStream() + ByteString(sBody),
ByteString());
}
void CPDFSDK_AppStream::SetAsListBox() {
CPDF_FormControl* pControl = widget_->GetFormControl();
CPDF_FormField* pField = pControl->GetField();
CFX_FloatRect rcClient = widget_->GetClientRect();
fxcrt::ostringstream sBody;
// Font map must outlive |pEdit|.
CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
widget_->GetPDFAnnot()->GetMutableAnnotDict(), "N");
auto pEdit = std::make_unique<CPWL_EditImpl>();
pEdit->EnableRefresh(false);
pEdit->SetFontMap(&font_map);
pEdit->SetPlateRect(CFX_FloatRect(rcClient.left, 0.0f, rcClient.right, 0.0f));
float fFontSize = widget_->GetFontSize();
pEdit->SetFontSize(FXSYS_IsFloatZero(fFontSize) ? 12.0f : fFontSize);
pEdit->Initialize();
fxcrt::ostringstream sList;
float fy = rcClient.top;
int32_t nTop = pField->GetTopVisibleIndex();
int32_t nCount = pField->CountOptions();
int32_t nSelCount = pField->CountSelectedItems();
for (int32_t i = nTop; i < nCount; ++i) {
bool bSelected = false;
for (int32_t j = 0; j < nSelCount; ++j) {
if (pField->GetSelectedIndex(j) == i) {
bSelected = true;
break;
}
}
pEdit->SetText(pField->GetOptionLabel(i));
pEdit->Paint();
CFX_FloatRect rcContent = pEdit->GetContentRect();
float fItemHeight = rcContent.Height();
if (bSelected) {
CFX_FloatRect rcItem =
CFX_FloatRect(rcClient.left, fy - fItemHeight, rcClient.right, fy);
{
AutoClosedQCommand q(&sList);
sList << GetFillColorAppStream(CFX_Color(
CFX_Color::Type::kRGB, 0, 51.0f / 255.0f, 113.0f / 255.0f));
WriteAppendRect(sList, rcItem);
sList << kFillOperator << "\n";
}
AutoClosedCommand bt(&sList, kTextBeginOperator, kTextEndOperator);
sList << GetFillColorAppStream(CFX_Color(CFX_Color::Type::kGray, 1))
<< GetEditAppStream(pEdit.get(), CFX_PointF(0.0f, fy), true, 0);
} else {
CFX_Color crText = widget_->GetTextPWLColor();
AutoClosedCommand bt(&sList, kTextBeginOperator, kTextEndOperator);
sList << GetFillColorAppStream(crText)
<< GetEditAppStream(pEdit.get(), CFX_PointF(0.0f, fy), true, 0);
}
fy -= fItemHeight;
}
if (sList.tellp() > 0) {
sBody << "/Tx ";
AutoClosedCommand bmc(&sBody, kMarkedSequenceBeginOperator,
kMarkedSequenceEndOperator);
AutoClosedQCommand q(&sBody);
WriteAppendRect(sBody, rcClient);
sBody << kSetNonZeroWindingClipOperator << "\n"
<< kEndPathNoFillOrStrokeOperator << "\n"
<< sList.str();
}
Write("N",
GetBackgroundAppStream() + GetBorderAppStream() + ByteString(sBody),
ByteString());
}
void CPDFSDK_AppStream::SetAsTextField(absl::optional<WideString> sValue) {
CPDF_FormControl* pControl = widget_->GetFormControl();
CPDF_FormField* pField = pControl->GetField();
fxcrt::ostringstream sBody;
fxcrt::ostringstream sLines;
// Font map must outlive |pEdit|.
CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
widget_->GetPDFAnnot()->GetMutableAnnotDict(), "N");
auto pEdit = std::make_unique<CPWL_EditImpl>();
pEdit->EnableRefresh(false);
pEdit->SetFontMap(&font_map);
CFX_FloatRect rcClient = widget_->GetClientRect();
pEdit->SetPlateRect(rcClient);
pEdit->SetAlignmentH(pControl->GetControlAlignment());
uint32_t dwFieldFlags = pField->GetFieldFlags();
bool bMultiLine = dwFieldFlags & pdfium::form_flags::kTextMultiline;
if (bMultiLine) {
pEdit->SetMultiLine(true);
pEdit->SetAutoReturn(true);
} else {
pEdit->SetAlignmentV(1);
}
uint16_t subWord = 0;
if (dwFieldFlags & pdfium::form_flags::kTextPassword) {
subWord = '*';
pEdit->SetPasswordChar(subWord);
}
int nMaxLen = pField->GetMaxLen();
bool bCharArray = dwFieldFlags & pdfium::form_flags::kTextComb;
float fFontSize = widget_->GetFontSize();
#ifdef PDF_ENABLE_XFA
if (!sValue.has_value() && widget_->GetMixXFAWidget())
sValue = widget_->GetValue();
#endif // PDF_ENABLE_XFA
if (nMaxLen > 0) {
if (bCharArray) {
pEdit->SetCharArray(nMaxLen);
if (FXSYS_IsFloatZero(fFontSize)) {
fFontSize = CPWL_Edit::GetCharArrayAutoFontSize(
font_map.GetPDFFont(0).Get(), rcClient, nMaxLen);
}
} else {
if (sValue.has_value())
nMaxLen = pdfium::base::checked_cast<int>(sValue.value().GetLength());
pEdit->SetLimitChar(nMaxLen);
}
}
if (FXSYS_IsFloatZero(fFontSize))
pEdit->SetAutoFontSize(true);
else
pEdit->SetFontSize(fFontSize);
pEdit->Initialize();
pEdit->SetText(sValue.value_or(pField->GetValue()));
pEdit->Paint();
CFX_FloatRect rcContent = pEdit->GetContentRect();
ByteString sEdit =
GetEditAppStream(pEdit.get(), CFX_PointF(), !bCharArray, subWord);
if (sEdit.GetLength() > 0) {
sBody << "/Tx ";
AutoClosedCommand bmc(&sBody, kMarkedSequenceBeginOperator,
kMarkedSequenceEndOperator);
AutoClosedQCommand q(&sBody);
if (rcContent.Width() > rcClient.Width() ||
rcContent.Height() > rcClient.Height()) {
WriteAppendRect(sBody, rcClient);
sBody << kSetNonZeroWindingClipOperator << "\n"
<< kEndPathNoFillOrStrokeOperator << "\n";
}
CFX_Color crText = widget_->GetTextPWLColor();
AutoClosedCommand bt(&sBody, kTextBeginOperator, kTextEndOperator);
sBody << GetFillColorAppStream(crText) << sEdit;
}
if (bCharArray) {
switch (widget_->GetBorderStyle()) {
case BorderStyle::kSolid: {
ByteString sColor =
GetStrokeColorAppStream(widget_->GetBorderPWLColor());
if (sColor.GetLength() > 0) {
AutoClosedQCommand q(&sLines);
sLines << widget_->GetBorderWidth() << " " << kSetLineWidthOperator
<< "\n"
<< GetStrokeColorAppStream(widget_->GetBorderPWLColor())
<< " 2 " << kSetLineCapStyleOperator << " 0 "
<< kSetLineJoinStyleOperator << "\n";
const float width = rcClient.right - rcClient.left;
for (int32_t i = 1; i < nMaxLen; ++i) {
const float left = rcClient.left + (width / nMaxLen) * i;
WriteMove(sLines, {left, rcClient.bottom});
WriteLine(sLines, {left, rcClient.top});
sLines << kStrokeOperator << "\n";
}
}
break;
}
case BorderStyle::kDash: {
ByteString sColor =
GetStrokeColorAppStream(widget_->GetBorderPWLColor());
if (sColor.GetLength() > 0) {
CPWL_Dash dsBorder = CPWL_Dash(3, 3, 0);
AutoClosedQCommand q(&sLines);
sLines << widget_->GetBorderWidth() << " " << kSetLineWidthOperator
<< "\n"
<< GetStrokeColorAppStream(widget_->GetBorderPWLColor()) << "["
<< dsBorder.nDash << " " << dsBorder.nGap << "] "
<< dsBorder.nPhase << " " << kSetDashOperator << "\n";
const float width = rcClient.right - rcClient.left;
for (int32_t i = 1; i < nMaxLen; ++i) {
const float left = rcClient.left + (width / nMaxLen) * i;
WriteMove(sLines, {left, rcClient.bottom});
WriteLine(sLines, {left, rcClient.top});
sLines << kStrokeOperator << "\n";
}
}
break;
}
default:
break;
}
}
Write("N",
GetBackgroundAppStream() + GetBorderAppStream() + ByteString(sLines) +
ByteString(sBody),
ByteString());
}
void CPDFSDK_AppStream::AddImage(const ByteString& sAPType,
CPDF_Stream* pImage) {
RetainPtr<CPDF_Stream> pStream = dict_->GetMutableStreamFor(sAPType);
RetainPtr<CPDF_Dictionary> pStreamDict = pStream->GetMutableDict();
ByteString sImageAlias = "IMG";
RetainPtr<const CPDF_Dictionary> pImageDict = pImage->GetDict();
if (pImageDict)
sImageAlias = pImageDict->GetByteStringFor("Name");
RetainPtr<CPDF_Dictionary> pStreamResList =
pStreamDict->GetOrCreateDictFor("Resources");
auto pXObject = pStreamResList->SetNewFor<CPDF_Dictionary>("XObject");
pXObject->SetNewFor<CPDF_Reference>(sImageAlias,
widget_->GetPageView()->GetPDFDocument(),
pImage->GetObjNum());
}
void CPDFSDK_AppStream::Write(const ByteString& sAPType,
const ByteString& sContents,
const ByteString& sAPState) {
RetainPtr<CPDF_Dictionary> pParentDict;
ByteString key;
if (sAPState.IsEmpty()) {
pParentDict = dict_;
key = sAPType;
} else {
pParentDict = dict_->GetOrCreateDictFor(sAPType);
key = sAPState;
}
RetainPtr<CPDF_Dictionary> pOrigStreamDict;
// If `pStream` is created by CreateModifiedAPStream(), then it is safe to
// edit, as it is not shared.
RetainPtr<CPDF_Stream> pStream = pParentDict->GetMutableStreamFor(key);
CPDF_Document* doc = widget_->GetPageView()->GetPDFDocument();
if (!doc->IsModifiedAPStream(pStream.Get())) {
if (pStream)
pOrigStreamDict = pStream->GetMutableDict();
pStream.Reset(doc->CreateModifiedAPStream());
pParentDict->SetNewFor<CPDF_Reference>(key, doc, pStream->GetObjNum());
}
RetainPtr<CPDF_Dictionary> pStreamDict = pStream->GetMutableDict();
if (!pStreamDict) {
pStreamDict = doc->New<CPDF_Dictionary>();
pStreamDict->SetNewFor<CPDF_Name>("Type", "XObject");
pStreamDict->SetNewFor<CPDF_Name>("Subtype", "Form");
pStreamDict->SetNewFor<CPDF_Number>("FormType", 1);
if (pOrigStreamDict) {
RetainPtr<const CPDF_Dictionary> pResources =
pOrigStreamDict->GetDictFor("Resources");
if (pResources)
pStreamDict->SetFor("Resources", pResources->Clone());
}
pStream->InitStreamWithEmptyData(pStreamDict);
}
pStreamDict->SetMatrixFor("Matrix", widget_->GetMatrix());
pStreamDict->SetRectFor("BBox", widget_->GetRotatedRect());
pStream->SetDataAndRemoveFilter(sContents.raw_span());
}
void CPDFSDK_AppStream::Remove(ByteStringView sAPType) {
dict_->RemoveFor(sAPType);
}
ByteString CPDFSDK_AppStream::GetBackgroundAppStream() const {
CFX_Color crBackground = widget_->GetFillPWLColor();
if (crBackground.nColorType != CFX_Color::Type::kTransparent)
return GetRectFillAppStream(widget_->GetRotatedRect(), crBackground);
return ByteString();
}
ByteString CPDFSDK_AppStream::GetBorderAppStream() const {
CFX_FloatRect rcWindow = widget_->GetRotatedRect();
CFX_Color crBorder = widget_->GetBorderPWLColor();
CFX_Color crBackground = widget_->GetFillPWLColor();
CFX_Color crLeftTop;
CFX_Color crRightBottom;
float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
CPWL_Dash dsBorder(3, 0, 0);
BorderStyle nBorderStyle = widget_->GetBorderStyle();
switch (nBorderStyle) {
case BorderStyle::kDash:
dsBorder = CPWL_Dash(3, 3, 0);
break;
case BorderStyle::kBeveled:
fBorderWidth *= 2;
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
crRightBottom = crBackground / 2.0f;
break;
case BorderStyle::kInset:
fBorderWidth *= 2;
crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
break;
default:
break;
}
return GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
crRightBottom, nBorderStyle, dsBorder);
}