зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
aec1a8ca85
Коммит
9c97d0c7bc
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче