From 6a7ee4905547404bc915f884377f59a4f82341f6 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Sat, 31 Jan 2015 18:17:12 +1100 Subject: [PATCH] Bug 569334 part 1 - Support getting font info in content query. r=masayuki,jfkthame,smaug --- dom/events/ContentEventHandler.cpp | 196 +++++++++++++++++++++++++++-- dom/events/ContentEventHandler.h | 18 +++ ipc/glue/IPCMessageUtils.h | 6 + widget/EventForwards.h | 3 + widget/FontRange.h | 27 ++++ widget/TextEvents.h | 12 ++ widget/moz.build | 1 + widget/nsGUIEventIPC.h | 26 +++- 8 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 widget/FontRange.h diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp index 93427c75e760..f5e83a2e89b2 100644 --- a/dom/events/ContentEventHandler.cpp +++ b/dom/events/ContentEventHandler.cpp @@ -314,6 +314,17 @@ ContentEventHandler::GetNativeTextLength(nsIContent* aContent, return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aMaxLength); } +static inline uint32_t +GetBRLength(LineBreakType aLineBreakType) +{ +#if defined(XP_WIN) + // Length of \r\n + return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1; +#else + return 1; +#endif +} + /* static */ uint32_t ContentEventHandler::GetTextLength(nsIContent* aContent, LineBreakType aLineBreakType, @@ -344,12 +355,7 @@ ContentEventHandler::GetTextLength(nsIContent* aContent, uint32_t length = std::min(text->GetLength(), aMaxLength); return length + textLengthDifference; } else if (IsContentBR(aContent)) { -#if defined(XP_WIN) - // Length of \r\n - return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1; -#else - return 1; -#endif + return GetBRLength(aLineBreakType); } return 0; } @@ -393,7 +399,6 @@ static nsresult GenerateFlatTextContent(nsRange* aRange, return NS_OK; } - nsAutoString tmpStr; for (; !iter->IsDone(); iter->Next()) { nsINode* node = iter->GetCurrentNode(); if (!node) { @@ -423,6 +428,171 @@ static nsresult GenerateFlatTextContent(nsRange* aRange, return NS_OK; } +static FontRange* +AppendFontRange(nsTArray& aFontRanges, uint32_t aBaseOffset) +{ + FontRange* fontRange = aFontRanges.AppendElement(); + fontRange->mStartOffset = aBaseOffset; + return fontRange; +} + +/* static */ uint32_t +ContentEventHandler::GetTextLengthInRange(nsIContent* aContent, + uint32_t aXPStartOffset, + uint32_t aXPEndOffset, + LineBreakType aLineBreakType) +{ + return aLineBreakType == LINE_BREAK_TYPE_NATIVE ? + GetNativeTextLength(aContent, aXPStartOffset, aXPEndOffset) : + aXPEndOffset - aXPStartOffset; +} + +/* static */ void +ContentEventHandler::AppendFontRanges(FontRangeArray& aFontRanges, + nsIContent* aContent, + int32_t aBaseOffset, + int32_t aXPStartOffset, + int32_t aXPEndOffset, + LineBreakType aLineBreakType) +{ + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (!frame) { + // It is a non-rendered content, create an empty range for it. + AppendFontRange(aFontRanges, aBaseOffset); + return; + } + + int32_t baseOffset = aBaseOffset; + nsTextFrame* curr = do_QueryFrame(frame); + MOZ_ASSERT(curr, "Not a text frame"); + while (curr) { + int32_t frameXPStart = std::max(curr->GetContentOffset(), aXPStartOffset); + int32_t frameXPEnd = std::min(curr->GetContentEnd(), aXPEndOffset); + if (frameXPStart >= frameXPEnd) { + curr = static_cast(curr->GetNextContinuation()); + continue; + } + + gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated); + + nsTextFrame* next = nullptr; + if (frameXPEnd < aXPEndOffset) { + next = static_cast(curr->GetNextContinuation()); + while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) { + frameXPEnd = std::min(next->GetContentEnd(), aXPEndOffset); + next = frameXPEnd < aXPEndOffset ? + static_cast(next->GetNextContinuation()) : nullptr; + } + } + + uint32_t skipStart = iter.ConvertOriginalToSkipped(frameXPStart); + uint32_t skipEnd = iter.ConvertOriginalToSkipped(frameXPEnd); + gfxTextRun::GlyphRunIterator runIter( + textRun, skipStart, skipEnd - skipStart); + int32_t lastXPEndOffset = frameXPStart; + while (runIter.NextRun()) { + gfxFont* font = runIter.GetGlyphRun()->mFont.get(); + int32_t startXPOffset = + iter.ConvertSkippedToOriginal(runIter.GetStringStart()); + // It is possible that the first glyph run has exceeded the frame, + // because the whole frame is filled by skipped chars. + if (startXPOffset >= frameXPEnd) { + break; + } + + if (startXPOffset > lastXPEndOffset) { + // Create range for skipped leading chars. + AppendFontRange(aFontRanges, baseOffset); + baseOffset += GetTextLengthInRange( + aContent, lastXPEndOffset, startXPOffset, aLineBreakType); + lastXPEndOffset = startXPOffset; + } + + FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset); + fontRange->mFontName = font->GetName(); + fontRange->mFontSize = font->GetAdjustedSize(); + + // The converted original offset may exceed the range, + // hence we need to clamp it. + int32_t endXPOffset = + iter.ConvertSkippedToOriginal(runIter.GetStringEnd()); + endXPOffset = std::min(frameXPEnd, endXPOffset); + baseOffset += GetTextLengthInRange(aContent, startXPOffset, endXPOffset, + aLineBreakType); + lastXPEndOffset = endXPOffset; + } + if (lastXPEndOffset < frameXPEnd) { + // Create range for skipped trailing chars. It also handles case + // that the whole frame contains only skipped chars. + AppendFontRange(aFontRanges, baseOffset); + baseOffset += GetTextLengthInRange( + aContent, lastXPEndOffset, frameXPEnd, aLineBreakType); + } + + curr = next; + } +} + +/* static */ nsresult +ContentEventHandler::GenerateFlatFontRanges(nsRange* aRange, + FontRangeArray& aFontRanges, + uint32_t& aLength, + LineBreakType aLineBreakType) +{ + MOZ_ASSERT(aFontRanges.IsEmpty(), "aRanges must be empty array"); + + nsINode* startNode = aRange->GetStartParent(); + nsINode* endNode = aRange->GetEndParent(); + if (NS_WARN_IF(!startNode || !endNode)) { + return NS_ERROR_FAILURE; + } + + // baseOffset is the flattened offset of each content node. + int32_t baseOffset = 0; + nsCOMPtr iter = NS_NewContentIterator(); + for (iter->Init(aRange); !iter->IsDone(); iter->Next()) { + nsINode* node = iter->GetCurrentNode(); + if (NS_WARN_IF(!node)) { + break; + } + if (!node->IsContent()) { + continue; + } + nsIContent* content = node->AsContent(); + + if (content->IsNodeOfType(nsINode::eTEXT)) { + int32_t startOffset = content != startNode ? 0 : aRange->StartOffset(); + int32_t endOffset = content != endNode ? + content->TextLength() : aRange->EndOffset(); + AppendFontRanges(aFontRanges, content, baseOffset, + startOffset, endOffset, aLineBreakType); + baseOffset += GetTextLengthInRange(content, startOffset, endOffset, + aLineBreakType); + } else if (IsContentBR(content)) { + if (aFontRanges.IsEmpty()) { + MOZ_ASSERT(baseOffset == 0); + FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset); + nsIFrame* frame = content->GetPrimaryFrame(); + if (frame) { + const nsFont& font = frame->GetParent()->StyleFont()->mFont; + const FontFamilyList& fontList = font.fontlist; + const FontFamilyName& fontName = fontList.IsEmpty() ? + FontFamilyName(fontList.GetDefaultFontType()) : + fontList.GetFontlist()[0]; + fontName.AppendToString(fontRange->mFontName, false); + fontRange->mFontSize = + frame->PresContext()->AppUnitsToDevPixels(font.size); + } + } + baseOffset += GetBRLength(aLineBreakType); + } + } + + aLength = baseOffset; + return NS_OK; +} + nsresult ContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent, bool aForward, @@ -697,6 +867,18 @@ ContentEventHandler::OnQueryTextContent(WidgetQueryContentEvent* aEvent) rv = GenerateFlatTextContent(range, aEvent->mReply.mString, lineBreakType); NS_ENSURE_SUCCESS(rv, rv); + if (aEvent->mWithFontRanges) { + uint32_t fontRangeLength; + rv = GenerateFlatFontRanges(range, aEvent->mReply.mFontRanges, + fontRangeLength, lineBreakType); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(fontRangeLength == aEvent->mReply.mString.Length(), + "Font ranges doesn't match the string"); + } + aEvent->mSucceeded = true; return NS_OK; diff --git a/dom/events/ContentEventHandler.h b/dom/events/ContentEventHandler.h index a86e56eee71f..f1c338b5cc3a 100644 --- a/dom/events/ContentEventHandler.h +++ b/dom/events/ContentEventHandler.h @@ -94,6 +94,12 @@ public: // Get the native text length of a content node excluding any children static uint32_t GetNativeTextLength(nsIContent* aContent, uint32_t aMaxLength = UINT32_MAX); + // Get the text length of a given range of a content node in + // the given line break type. + static uint32_t GetTextLengthInRange(nsIContent* aContent, + uint32_t aXPStartOffset, + uint32_t aXPEndOffset, + LineBreakType aLineBreakType); protected: static uint32_t GetTextLength(nsIContent* aContent, LineBreakType aLineBreakType, @@ -129,6 +135,18 @@ protected: // true, it is expanded to forward. nsresult ExpandToClusterBoundary(nsIContent* aContent, bool aForward, uint32_t* aXPOffset); + + typedef nsTArray FontRangeArray; + static void AppendFontRanges(FontRangeArray& aFontRanges, + nsIContent* aContent, + int32_t aBaseOffset, + int32_t aXPStartOffset, + int32_t aXPEndOffset, + LineBreakType aLineBreakType); + static nsresult GenerateFlatFontRanges(nsRange* aRange, + FontRangeArray& aFontRanges, + uint32_t& aLength, + LineBreakType aLineBreakType); }; } // namespace mozilla diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h index a1d0d7629a9d..673d905c4def 100644 --- a/ipc/glue/IPCMessageUtils.h +++ b/ipc/glue/IPCMessageUtils.h @@ -544,6 +544,12 @@ struct ParamTraits > } }; +template +struct ParamTraits> : ParamTraits> +{ + typedef nsAutoTArray paramType; +}; + template<> struct ParamTraits { diff --git a/widget/EventForwards.h b/widget/EventForwards.h index 72a768c53af3..25567b55b4f0 100644 --- a/widget/EventForwards.h +++ b/widget/EventForwards.h @@ -112,6 +112,9 @@ struct TextRange; class TextRangeArray; +// FontRange.h +struct FontRange; + } // namespace mozilla #endif // mozilla_EventForwards_h__ diff --git a/widget/FontRange.h b/widget/FontRange.h new file mode 100644 index 000000000000..e2de0f4a1422 --- /dev/null +++ b/widget/FontRange.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_FontRange_h_ +#define mozilla_FontRange_h_ + +namespace mozilla { + +struct FontRange +{ + FontRange() + : mStartOffset(0) + , mFontSize(0) + { + } + + int32_t mStartOffset; + nsString mFontName; + gfxFloat mFontSize; // in device pixels +}; + +} + +#endif // mozilla_FontRange_h_ diff --git a/widget/TextEvents.h b/widget/TextEvents.h index 957a7b15ac5e..1367b5c38cec 100644 --- a/widget/TextEvents.h +++ b/widget/TextEvents.h @@ -12,6 +12,7 @@ #include "mozilla/BasicEvents.h" #include "mozilla/EventForwards.h" // for KeyNameIndex, temporarily #include "mozilla/TextRange.h" +#include "mozilla/FontRange.h" #include "nsCOMPtr.h" #include "nsIDOMKeyEvent.h" #include "nsITransferable.h" @@ -399,6 +400,7 @@ public: , mSucceeded(false) , mWasAsync(false) , mUseNativeLineBreak(true) + , mWithFontRanges(false) { } @@ -447,6 +449,13 @@ public: refPoint = aPoint; } + void RequestFontRanges() + { + NS_ASSERTION(message == NS_QUERY_TEXT_CONTENT, + "not querying text content"); + mWithFontRanges = true; + } + uint32_t GetSelectionStart(void) const { NS_ASSERTION(message == NS_QUERY_SELECTED_TEXT, @@ -471,6 +480,7 @@ public: bool mSucceeded; bool mWasAsync; bool mUseNativeLineBreak; + bool mWithFontRanges; struct { uint32_t mOffset; @@ -495,6 +505,8 @@ public: mozilla::WritingMode mWritingMode; // used by NS_QUERY_SELECTION_AS_TRANSFERABLE nsCOMPtr mTransferable; + // used by NS_QUERY_TEXT_CONTENT with font ranges requested + nsAutoTArray mFontRanges; } mReply; enum diff --git a/widget/moz.build b/widget/moz.build index 422db4cc47e5..71c9beacc853 100644 --- a/widget/moz.build +++ b/widget/moz.build @@ -117,6 +117,7 @@ EXPORTS.mozilla += [ 'ContentEvents.h', 'EventClassList.h', 'EventForwards.h', + 'FontRange.h', 'LookAndFeel.h', 'MiscEvents.h', 'MouseEvents.h', diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h index 82b9ceec5fd4..bd17ccaf0491 100644 --- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -525,6 +525,26 @@ struct ParamTraits } }; +template<> +struct ParamTraits +{ + typedef mozilla::FontRange paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mStartOffset); + WriteParam(aMsg, aParam.mFontName); + WriteParam(aMsg, aParam.mFontSize); + } + + static bool Read(const Message* aMsg, void** aIter, paramType* aResult) + { + return ReadParam(aMsg, aIter, &aResult->mStartOffset) && + ReadParam(aMsg, aIter, &aResult->mFontName) && + ReadParam(aMsg, aIter, &aResult->mFontSize); + } +}; + template<> struct ParamTraits { @@ -535,6 +555,7 @@ struct ParamTraits WriteParam(aMsg, static_cast(aParam)); WriteParam(aMsg, aParam.mSucceeded); WriteParam(aMsg, aParam.mUseNativeLineBreak); + WriteParam(aMsg, aParam.mWithFontRanges); WriteParam(aMsg, aParam.mInput.mOffset); WriteParam(aMsg, aParam.mInput.mLength); WriteParam(aMsg, aParam.mReply.mOffset); @@ -543,6 +564,7 @@ struct ParamTraits WriteParam(aMsg, aParam.mReply.mReversed); WriteParam(aMsg, aParam.mReply.mHasSelection); WriteParam(aMsg, aParam.mReply.mWidgetIsHit); + WriteParam(aMsg, aParam.mReply.mFontRanges); } static bool Read(const Message* aMsg, void** aIter, paramType* aResult) @@ -552,6 +574,7 @@ struct ParamTraits static_cast(aResult)) && ReadParam(aMsg, aIter, &aResult->mSucceeded) && ReadParam(aMsg, aIter, &aResult->mUseNativeLineBreak) && + ReadParam(aMsg, aIter, &aResult->mWithFontRanges) && ReadParam(aMsg, aIter, &aResult->mInput.mOffset) && ReadParam(aMsg, aIter, &aResult->mInput.mLength) && ReadParam(aMsg, aIter, &aResult->mReply.mOffset) && @@ -559,7 +582,8 @@ struct ParamTraits ReadParam(aMsg, aIter, &aResult->mReply.mRect) && ReadParam(aMsg, aIter, &aResult->mReply.mReversed) && ReadParam(aMsg, aIter, &aResult->mReply.mHasSelection) && - ReadParam(aMsg, aIter, &aResult->mReply.mWidgetIsHit); + ReadParam(aMsg, aIter, &aResult->mReply.mWidgetIsHit) && + ReadParam(aMsg, aIter, &aResult->mReply.mFontRanges); } };