Bug 1722300 - patch 4 - Implement internal destinations when generating PDF output through cairo. r=mattwoodrow

Differential Revision: https://phabricator.services.mozilla.com/D120960
This commit is contained in:
Jonathan Kew 2021-08-04 12:52:38 +00:00
Родитель aec1a8ca85
Коммит 9c97d0c7bc
12 изменённых файлов: 210 добавлений и 24 удалений

Просмотреть файл

@ -1081,6 +1081,7 @@ class DrawTarget : public external::AtomicRefCounted<DrawTarget> {
* Method to generate hyperlink in PDF output (with appropriate backend).
*/
virtual void Link(const char* aDestination, const Rect& aRect) {}
virtual void Destination(const char* aDestination, const Point& aPoint) {}
/**
* Returns a SourceSurface which is a snapshot of the current contents of the

Просмотреть файл

@ -678,8 +678,13 @@ void DrawTargetCairo::Link(const char* aDestination, const Rect& aRect) {
cairo_user_to_device(mContext, &x, &y);
cairo_user_to_device_distance(mContext, &w, &h);
nsPrintfCString attributes("rect=[%f %f %f %f] uri='%s'", x, y, w, h,
dest.get());
nsPrintfCString attributes("rect=[%f %f %f %f] ", x, y, w, h);
if (dest[0] == '#') {
// The actual destination does not have a leading '#'.
attributes.AppendPrintf("dest='%s'", dest.get() + 1);
} else {
attributes.AppendPrintf("uri='%s'", dest.get());
}
// We generate a begin/end pair with no content in between, because we are
// using the rect attribute of the begin tag to specify the link region
@ -688,6 +693,29 @@ void DrawTargetCairo::Link(const char* aDestination, const Rect& aRect) {
cairo_tag_end(mContext, CAIRO_TAG_LINK);
}
void DrawTargetCairo::Destination(const char* aDestination,
const Point& aPoint) {
if (!aDestination || !*aDestination) {
// No destination? Just bail out.
return;
}
nsAutoCString dest(aDestination);
for (size_t i = dest.Length(); i > 0;) {
--i;
if (dest[i] == '\'') {
dest.ReplaceLiteral(i, 1, "\\'");
}
}
double x = aPoint.x, y = aPoint.y;
cairo_user_to_device(mContext, &x, &y);
nsPrintfCString attributes("name='%s' x=%f y=%f internal", dest.get(), x, y);
cairo_tag_begin(mContext, CAIRO_TAG_DEST, attributes.get());
cairo_tag_end(mContext, CAIRO_TAG_DEST);
}
already_AddRefed<SourceSurface> DrawTargetCairo::Snapshot() {
if (!IsValid()) {
gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface "

Просмотреть файл

@ -61,6 +61,8 @@ class DrawTargetCairo final : public DrawTarget {
}
virtual void Link(const char* aDestination, const Rect& aRect) override;
virtual void Destination(const char* aDestination,
const Point& aPoint) override;
virtual already_AddRefed<SourceSurface> Snapshot() override;
virtual IntSize GetSize() const override;

Просмотреть файл

@ -202,6 +202,11 @@ void DrawTargetRecording::Link(const char* aDestination, const Rect& aRect) {
mRecorder->RecordEvent(RecordedLink(this, aDestination, aRect));
}
void DrawTargetRecording::Destination(const char* aDestination,
const Point& aPoint) {
mRecorder->RecordEvent(RecordedDestination(this, aDestination, aPoint));
}
void DrawTargetRecording::FillRect(const Rect& aRect, const Pattern& aPattern,
const DrawOptions& aOptions) {
EnsurePatternDependenciesStored(aPattern);

Просмотреть файл

@ -30,6 +30,8 @@ class DrawTargetRecording : public DrawTarget {
virtual bool IsRecording() const override { return true; }
virtual void Link(const char* aDestination, const Rect& aRect) override;
virtual void Destination(const char* aDestination,
const Point& aPoint) override;
virtual already_AddRefed<SourceSurface> Snapshot() override;
virtual already_AddRefed<SourceSurface> IntoLuminanceSource(

Просмотреть файл

@ -117,6 +117,8 @@ std::string RecordedEvent::GetEventName(EventType aType) {
return "ExternalSourceSurfaceCreation";
case LINK:
return "Link";
case DESTINATION:
return "Destination";
default:
return "Unknown";
}

Просмотреть файл

@ -30,7 +30,7 @@ const uint32_t kMagicInt = 0xc001feed;
const uint16_t kMajorRevision = 10;
// A change in minor revision means additions of new events. New streams will
// not play in older players.
const uint16_t kMinorRevision = 2;
const uint16_t kMinorRevision = 3;
struct ReferencePtr {
ReferencePtr() : mLongPtr(0) {}
@ -392,6 +392,7 @@ class RecordedEvent {
DETACHALLSNAPSHOTS,
OPTIMIZESOURCESURFACE,
LINK,
DESTINATION,
LAST,
};

Просмотреть файл

@ -1553,6 +1553,31 @@ class RecordedLink : public RecordedDrawingEvent<RecordedLink> {
MOZ_IMPLICIT RecordedLink(S& aStream);
};
class RecordedDestination : public RecordedDrawingEvent<RecordedDestination> {
public:
RecordedDestination(DrawTarget* aDT, const char* aDestination,
const Point& aPoint)
: RecordedDrawingEvent(DESTINATION, aDT),
mDestination(aDestination),
mPoint(aPoint) {}
bool PlayEvent(Translator* aTranslator) const override;
template <class S>
void Record(S& aStream) const;
void OutputSimpleEventInfo(std::stringstream& aStringStream) const override;
std::string GetName() const override { return "Destination"; }
private:
friend class RecordedEvent;
std::string mDestination;
Point mPoint;
template <class S>
MOZ_IMPLICIT RecordedDestination(S& aStream);
};
static std::string NameFromBackend(BackendType aType) {
switch (aType) {
case BackendType::NONE:
@ -3923,6 +3948,43 @@ inline void RecordedLink::OutputSimpleEventInfo(
aStringStream << "Link [" << mDestination << " @ " << mRect << "]";
}
inline bool RecordedDestination::PlayEvent(Translator* aTranslator) const {
DrawTarget* dt = aTranslator->LookupDrawTarget(mDT);
if (!dt) {
return false;
}
dt->Destination(mDestination.c_str(), mPoint);
return true;
}
template <class S>
void RecordedDestination::Record(S& aStream) const {
RecordedDrawingEvent::Record(aStream);
WriteElement(aStream, mPoint);
uint32_t len = mDestination.length();
WriteElement(aStream, len);
if (len) {
aStream.write(mDestination.data(), len);
}
}
template <class S>
RecordedDestination::RecordedDestination(S& aStream)
: RecordedDrawingEvent(DESTINATION, aStream) {
ReadElement(aStream, mPoint);
uint32_t len;
ReadElement(aStream, len);
mDestination.resize(size_t(len));
if (len && aStream.good()) {
aStream.read(&mDestination.front(), len);
}
}
inline void RecordedDestination::OutputSimpleEventInfo(
std::stringstream& aStringStream) const {
aStringStream << "Destination [" << mDestination << " @ " << mPoint << "]";
}
#define FOR_EACH_EVENT(f) \
f(DRAWTARGETCREATION, RecordedDrawTargetCreation); \
f(DRAWTARGETDESTRUCTION, RecordedDrawTargetDestruction); \
@ -3972,7 +4034,8 @@ inline void RecordedLink::OutputSimpleEventInfo(
f(FLUSH, RecordedFlush); \
f(DETACHALLSNAPSHOTS, RecordedDetachAllSnapshots); \
f(OPTIMIZESOURCESURFACE, RecordedOptimizeSourceSurface); \
f(LINK, RecordedLink);
f(LINK, RecordedLink); \
f(DESTINATION, RecordedDestination);
#define DO_WITH_EVENT_TYPE(_typeenum, _class) \
case _typeenum: { \

Просмотреть файл

@ -4004,8 +4004,8 @@ void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
Maybe<nsDisplayListBuilder::Linkifier> linkifier;
if (StaticPrefs::print_save_as_pdf_links_enabled() &&
aBuilder->IsForPrinting()) {
linkifier.emplace(aBuilder, aChild);
linkifier->MaybeAppendLink(aBuilder, aChild, aLists.Content());
linkifier.emplace(aBuilder, aChild, aLists.Content());
linkifier->MaybeAppendLink(aBuilder, aChild);
}
nsIFrame* child = aChild;

Просмотреть файл

@ -37,6 +37,7 @@ DECLARE_DISPLAY_ITEM_TYPE(COLUMN_RULE, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(COMBOBOX_FOCUS, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(COMPOSITOR_HITTEST_INFO, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(CONTAINER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
DECLARE_DISPLAY_ITEM_TYPE(DESTINATION, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(EVENT_RECEIVER, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(FIELDSET_BORDER_BACKGROUND, 0)
DECLARE_DISPLAY_ITEM_TYPE(FILTER, TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)

Просмотреть файл

@ -552,13 +552,9 @@ nsRect nsDisplayListBuilder::OutOfFlowDisplayData::ComputeVisibleRectForFrame(
}
nsDisplayListBuilder::Linkifier::Linkifier(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame) {
// Links don't nest, so if the builder already has a destination, no need to
// check for a link element here.
if (!aBuilder->mLinkSpec.IsEmpty()) {
return;
}
nsIFrame* aFrame,
nsDisplayList* aList)
: mList(aList) {
// Find the element that we need to check for link-ness, bailing out if
// we can't find one.
Element* elem = Element::FromNodeOrNull(aFrame->GetContent());
@ -566,29 +562,80 @@ nsDisplayListBuilder::Linkifier::Linkifier(nsDisplayListBuilder* aBuilder,
return;
}
// Check if we have actually found a link and it has a usable spec.
nsCOMPtr<nsIURI> linkURI;
if (!elem->IsLink(getter_AddRefs(linkURI))) {
// If the element has an id and/or name attribute, generate a destination
// for possible internal linking.
auto maybeGenerateDest = [&](const nsAtom* aAttr) {
nsAutoString attrValue;
elem->GetAttr(aAttr, attrValue);
if (!attrValue.IsEmpty()) {
NS_ConvertUTF16toUTF8 dest(attrValue);
// Ensure that we only emit a given destination once, although there may
// be multiple frames associated with a given element; we'll simply use
// the first of them as the target of any links to it.
// XXX(jfkthame) This prevents emitting duplicate destinations *on the
// same page*, but does not prevent duplicates on subsequent pages, as
// each new page is handled by a new temporary DisplayListBuilder. This
// seems to be harmless in practice, though a bit wasteful of space. To
// fix, we need to maintain the set of already-seen destinations globally
// for the print job, rather than attached to the (per-page) builder.
if (aBuilder->mDestinations.EnsureInserted(dest)) {
auto* destination = MakeDisplayItem<nsDisplayDestination>(
aBuilder, aFrame, dest.get(), aFrame->GetRect().TopLeft());
mList->AppendToTop(destination);
}
}
};
if (elem->HasID()) {
maybeGenerateDest(nsGkAtoms::id);
}
if (elem->HasName()) {
maybeGenerateDest(nsGkAtoms::name);
}
// Links don't nest, so if the builder already has a destination, no need to
// check for a link element here.
if (!aBuilder->mLinkSpec.IsEmpty()) {
return;
}
if (NS_FAILED(linkURI->GetSpec(aBuilder->mLinkSpec)) ||
aBuilder->mLinkSpec.IsEmpty()) {
// Check if we have actually found a link.
nsCOMPtr<nsIURI> uri;
if (!elem->IsLink(getter_AddRefs(uri))) {
return;
}
// Is it a local (in-page) destination?
bool hasRef, eqExRef;
nsIURI* docURI;
if (NS_SUCCEEDED(uri->GetHasRef(&hasRef)) && hasRef &&
(docURI = aFrame->PresContext()->Document()->GetDocumentURI()) &&
NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &eqExRef)) && eqExRef) {
if (NS_FAILED(uri->GetRef(aBuilder->mLinkSpec)) ||
aBuilder->mLinkSpec.IsEmpty()) {
return;
}
// Mark the link spec as being an internal destination
aBuilder->mLinkSpec.Insert('#', 0);
} else {
if (NS_FAILED(uri->GetSpec(aBuilder->mLinkSpec)) ||
aBuilder->mLinkSpec.IsEmpty()) {
return;
}
}
// Record that we need to reset the builder's state on destruction.
mBuilderToReset = aBuilder;
}
void nsDisplayListBuilder::Linkifier::MaybeAppendLink(
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList) {
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
// Note that we may generate a link here even if the constructor bailed out
// without updating aBuilder->LinkSpec(), because it may have been set by
// an ancestor that was associated with a link element.
if (!aBuilder->mLinkSpec.IsEmpty()) {
auto* link = MakeDisplayItem<nsDisplayLink>(
aBuilder, aFrame, aBuilder->mLinkSpec.get(), aFrame->GetRect());
aList->AppendToTop(link);
mList->AppendToTop(link);
}
}
@ -8323,7 +8370,9 @@ void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
}
gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
Matrix4x4 trans = ShouldSkipTransform(aBuilder) ? Matrix4x4() : GetAccumulatedPreserved3DTransform(aBuilder);
Matrix4x4 trans = ShouldSkipTransform(aBuilder)
? Matrix4x4()
: GetAccumulatedPreserved3DTransform(aBuilder);
if (!IsFrameVisible(mFrame, trans)) {
return;
}
@ -10347,6 +10396,14 @@ void nsDisplayLink::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
NSRectToRect(GetPaintRect(), appPerDev));
}
void nsDisplayDestination::Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx) {
auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel();
aCtx->GetDrawTarget()->Destination(
mDestinationName.get(),
NSPointToPoint(GetPaintRect().TopLeft(), appPerDev));
}
void nsDisplayListCollection::SerializeWithCorrectZOrder(
nsDisplayList* aOutResultList, nsIContent* aContent) {
// Sort PositionedDescendants() in CSS 'z-order' order. The list is already

Просмотреть файл

@ -1787,9 +1787,12 @@ class nsDisplayListBuilder {
// Helper class to find what link spec (if any) to associate with a frame,
// recording it in the builder, and generate the corresponding DisplayItem.
// This also takes care of generating a named destination for internal links
// if the element has an id or name attribute.
class Linkifier {
public:
Linkifier(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
Linkifier(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList);
~Linkifier() {
if (mBuilderToReset) {
@ -1797,11 +1800,11 @@ class nsDisplayListBuilder {
}
}
void MaybeAppendLink(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList);
void MaybeAppendLink(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
private:
nsDisplayListBuilder* mBuilderToReset = nullptr;
nsDisplayList* mList;
};
private:
@ -1984,6 +1987,7 @@ class nsDisplayListBuilder {
const ActiveScrolledRoot* mFilterASR;
std::unordered_set<nsIScrollableFrame*> mScrollFramesToNotify;
nsCString mLinkSpec; // Destination of link currently being emitted, if any.
nsTHashSet<nsCString> mDestinations; // Destination names emitted.
bool mContainsBlendMode;
bool mIsBuildingScrollbar;
bool mCurrentScrollbarWillHaveLayer;
@ -7307,6 +7311,26 @@ class nsDisplayLink : public nsPaintedDisplayItem {
nsRect mRect;
};
/**
* A display item to represent a destination within the document.
*/
class nsDisplayDestination : public nsPaintedDisplayItem {
public:
nsDisplayDestination(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
const char* aDestinationName, const nsPoint& aPosition)
: nsPaintedDisplayItem(aBuilder, aFrame),
mDestinationName(aDestinationName),
mPosition(aPosition) {}
NS_DISPLAY_DECL_NAME("Destination", TYPE_DESTINATION)
void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
private:
nsCString mDestinationName;
nsPoint mPosition;
};
class FlattenedDisplayListIterator {
public:
FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder,