/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code is subject to the terms of the Mozilla Public License * version 2.0 (the "License"). You can obtain a copy of the License at * http://mozilla.org/MPL/2.0/. */ /* rendering object for CSS "display: ruby" */ #include "nsRubyFrame.h" #include "nsLineLayout.h" #include "nsPresContext.h" #include "nsStyleContext.h" #include "WritingModes.h" #include "nsRubyBaseContainerFrame.h" #include "nsRubyTextContainerFrame.h" using namespace mozilla; //---------------------------------------------------------------------- // Frame class boilerplate // ======================= NS_QUERYFRAME_HEAD(nsRubyFrame) NS_QUERYFRAME_ENTRY(nsRubyFrame) NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) NS_IMPL_FRAMEARENA_HELPERS(nsRubyFrame) nsContainerFrame* NS_NewRubyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) { return new (aPresShell) nsRubyFrame(aContext); } //---------------------------------------------------------------------- // nsRubyFrame Method Implementations // ================================== nsIAtom* nsRubyFrame::GetType() const { return nsGkAtoms::rubyFrame; } /* virtual */ bool nsRubyFrame::IsFrameOfType(uint32_t aFlags) const { return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eLineParticipant)); } #ifdef DEBUG_FRAME_DUMP nsresult nsRubyFrame::GetFrameName(nsAString& aResult) const { return MakeFrameName(NS_LITERAL_STRING("Ruby"), aResult); } #endif class MOZ_STACK_CLASS TextContainerIterator { public: TextContainerIterator(nsRubyBaseContainerFrame* aBaseContainer); void Next(); bool AtEnd() const { return !mFrame; } nsRubyTextContainerFrame* GetTextContainer() const { return static_cast(mFrame); } private: nsIFrame* mFrame; }; TextContainerIterator::TextContainerIterator( nsRubyBaseContainerFrame* aBaseContainer) { mFrame = aBaseContainer; Next(); } void TextContainerIterator::Next() { if (mFrame) { mFrame = mFrame->GetNextSibling(); if (mFrame && mFrame->GetType() != nsGkAtoms::rubyTextContainerFrame) { mFrame = nullptr; } } } /** * This class is responsible for appending and clearing * text container list of the base container. */ class MOZ_STACK_CLASS AutoSetTextContainers { public: AutoSetTextContainers(nsRubyBaseContainerFrame* aBaseContainer); ~AutoSetTextContainers(); private: nsRubyBaseContainerFrame* mBaseContainer; }; AutoSetTextContainers::AutoSetTextContainers( nsRubyBaseContainerFrame* aBaseContainer) : mBaseContainer(aBaseContainer) { #ifdef DEBUG aBaseContainer->AssertTextContainersEmpty(); #endif for (TextContainerIterator iter(aBaseContainer); !iter.AtEnd(); iter.Next()) { aBaseContainer->AppendTextContainer(iter.GetTextContainer()); } } AutoSetTextContainers::~AutoSetTextContainers() { mBaseContainer->ClearTextContainers(); } /** * This enumerator enumerates each segment. */ class MOZ_STACK_CLASS SegmentEnumerator { public: SegmentEnumerator(nsRubyFrame* aRubyFrame); void Next(); bool AtEnd() const { return !mBaseContainer; } nsRubyBaseContainerFrame* GetBaseContainer() const { return mBaseContainer; } private: nsRubyBaseContainerFrame* mBaseContainer; }; SegmentEnumerator::SegmentEnumerator(nsRubyFrame* aRubyFrame) { nsIFrame* frame = aRubyFrame->GetFirstPrincipalChild(); MOZ_ASSERT(!frame || frame->GetType() == nsGkAtoms::rubyBaseContainerFrame); mBaseContainer = static_cast(frame); } void SegmentEnumerator::Next() { MOZ_ASSERT(mBaseContainer); nsIFrame* frame = mBaseContainer->GetNextSibling(); while (frame && frame->GetType() != nsGkAtoms::rubyBaseContainerFrame) { frame = frame->GetNextSibling(); } mBaseContainer = static_cast(frame); } /* virtual */ void nsRubyFrame::AddInlineMinISize(nsRenderingContext *aRenderingContext, nsIFrame::InlineMinISizeData *aData) { nscoord max = 0; for (SegmentEnumerator e(this); !e.AtEnd(); e.Next()) { AutoSetTextContainers holder(e.GetBaseContainer()); max = std::max(max, e.GetBaseContainer()->GetMinISize(aRenderingContext)); } aData->currentLine += max; } /* virtual */ void nsRubyFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext, nsIFrame::InlinePrefISizeData *aData) { nscoord sum = 0; for (SegmentEnumerator e(this); !e.AtEnd(); e.Next()) { AutoSetTextContainers holder(e.GetBaseContainer()); sum += e.GetBaseContainer()->GetPrefISize(aRenderingContext); } aData->currentLine += sum; } /* virtual */ LogicalSize nsRubyFrame::ComputeSize(nsRenderingContext *aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, nscoord aAvailableISize, const LogicalSize& aMargin, const LogicalSize& aBorder, const LogicalSize& aPadding, ComputeSizeFlags aFlags) { // Ruby frame is inline, hence don't compute size before reflow. return LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); } /* virtual */ nscoord nsRubyFrame::GetLogicalBaseline(WritingMode aWritingMode) const { return mBaseline; } /* virtual */ bool nsRubyFrame::CanContinueTextRun() const { return true; } /* virtual */ void nsRubyFrame::Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { DO_GLOBAL_REFLOW_COUNT("nsRubyFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); if (!aReflowState.mLineLayout) { NS_ASSERTION(aReflowState.mLineLayout, "No line layout provided to RubyFrame reflow method."); aStatus = NS_FRAME_COMPLETE; return; } // Begin the span for the ruby frame WritingMode frameWM = aReflowState.GetWritingMode(); WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode(); LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding(); nscoord startEdge = borderPadding.IStart(frameWM); nscoord endEdge = aReflowState.AvailableISize() - borderPadding.IEnd(frameWM); NS_ASSERTION(aReflowState.AvailableISize() != NS_UNCONSTRAINEDSIZE, "should no longer use available widths"); aReflowState.mLineLayout->BeginSpan(this, &aReflowState, startEdge, endEdge, &mBaseline); // FIXME: line breaking / continuations not yet implemented aStatus = NS_FRAME_COMPLETE; LogicalSize availSize(lineWM, aReflowState.AvailableISize(), aReflowState.AvailableBSize()); for (SegmentEnumerator e(this); !e.AtEnd(); e.Next()) { nsRubyBaseContainerFrame* baseContainer = e.GetBaseContainer(); AutoSetTextContainers holder(baseContainer); nsReflowStatus baseReflowStatus; nsHTMLReflowMetrics baseMetrics(aReflowState, aDesiredSize.mFlags); bool pushedFrame; aReflowState.mLineLayout->ReflowFrame(baseContainer, baseReflowStatus, &baseMetrics, pushedFrame); nsRect baseRect = baseContainer->GetRect(); for (TextContainerIterator iter(baseContainer); !iter.AtEnd(); iter.Next()) { nsRubyTextContainerFrame* textContainer = iter.GetTextContainer(); nsReflowStatus textReflowStatus; nsHTMLReflowMetrics textMetrics(aReflowState, aDesiredSize.mFlags); nsHTMLReflowState textReflowState(aPresContext, aReflowState, textContainer, availSize); textReflowState.mLineLayout = aReflowState.mLineLayout; textContainer->Reflow(aPresContext, textMetrics, textReflowState, textReflowStatus); NS_ASSERTION(textReflowStatus == NS_FRAME_COMPLETE, "Ruby line breaking is not yet implemented"); textContainer->SetSize(LogicalSize(lineWM, textMetrics.ISize(lineWM), textMetrics.BSize(lineWM))); nscoord x, y; nscoord bsize = textMetrics.BSize(lineWM); if (lineWM.IsVertical()) { x = lineWM.IsVerticalLR() ? -bsize : baseRect.XMost(); y = baseRect.Y(); } else { x = baseRect.X(); y = -bsize; } FinishReflowChild(textContainer, aPresContext, textMetrics, &textReflowState, x, y, 0); } } aDesiredSize.ISize(lineWM) = aReflowState.mLineLayout->EndSpan(this); nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState, borderPadding, lineWM, frameWM); }