Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  nsLineLayout.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; 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/. */


/* state and methods used while laying out a single line of a block frame */

#include "nsLineLayout.h"

#include "mozilla/ComputedStyle.h"
#include "mozilla/SVGTextFrame.h"

#include "LayoutLogging.h"
#include "nsBidiPresUtils.h"
#include "nsBlockFrame.h"
#include "nsContainerFrame.h"
#include "nsFloatManager.h"
#include "nsFontMetrics.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsRubyFrame.h"
#include "nsRubyTextFrame.h"
#include "nsStyleConsts.h"
#include "nsStyleStructInlines.h"
#include "nsTextFrame.h"
#include "RubyUtils.h"
#include <algorithm>

#ifdef DEBUG
#  undef NOISY_INLINEDIR_ALIGN
#  undef NOISY_BLOCKDIR_ALIGN
#  undef NOISY_REFLOW
#  undef REALLY_NOISY_REFLOW
#  undef NOISY_PUSHING
#  undef REALLY_NOISY_PUSHING
#  undef NOISY_CAN_PLACE_FRAME
#  undef NOISY_TRIM
#  undef REALLY_NOISY_TRIM
#endif

using namespace mozilla;

//----------------------------------------------------------------------

nsLineLayout::nsLineLayout(nsPresContext* aPresContext,
                           nsFloatManager* aFloatManager,
                           const ReflowInput& aLineContainerRI,
                           const nsLineList::iterator* aLine,
                           nsLineLayout* aBaseLineLayout)
    : mPresContext(aPresContext),
      mFloatManager(aFloatManager),
      mLineContainerRI(aLineContainerRI),
      mBaseLineLayout(aBaseLineLayout),
      mFirstLetterStyleOK(false),
      mIsTopOfPage(false),
      mImpactedByFloats(false),
      mLastFloatWasLetterFrame(false),
      mLineIsEmpty(false),
      mLineEndsInBR(false),
      mNeedBackup(false),
      mInFirstLine(false),
      mGotLineBox(false),
      mInFirstLetter(false),
      mHasMarker(false),
      mDirtyNextLine(false),
      mLineAtStart(false),
      mHasRuby(false),
      mSuppressLineWrap(LineContainerFrame()->IsInSVGTextSubtree()),
      mUsedOverflowWrap(false) {
  NS_ASSERTION(aFloatManager || LineContainerFrame()->IsLetterFrame(),
               "float manager should be present");
  MOZ_ASSERT(
      !!mBaseLineLayout == LineContainerFrame()->IsRubyTextContainerFrame(),
      "Only ruby text container frames have a different base line layout");
  MOZ_COUNT_CTOR(nsLineLayout);

  // Stash away some style data that we need
  nsBlockFrame* blockFrame = do_QueryFrame(LineContainerFrame());
  mStyleText = blockFrame ? blockFrame->StyleTextForLineLayout()
                          : LineContainerFrame()->StyleText();

  mInflationMinFontSize =
      nsLayoutUtils::InflationMinFontSizeFor(LineContainerFrame());

  if (aLine) {
    mGotLineBox = true;
    mLineBox = *aLine;
  }
}

void nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord,
                                   nscoord aISize, nscoord aBSize,
                                   bool aImpactedByFloats, bool aIsTopOfPage,
                                   WritingMode aWritingMode,
                                   const nsSize& aContainerSize,
                                   nscoord aInset) {
  MOZ_ASSERT(nullptr == mRootSpan, "bad linelayout user");
  LAYOUT_WARN_IF_FALSE(aISize != NS_UNCONSTRAINEDSIZE,
                       "have unconstrained width; this should only result from "
                       "very large sizes, not attempts at intrinsic width "
                       "calculation");
#ifdef DEBUG
  if ((aISize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aISize) &&
      !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
    LineContainerFrame()->ListTag(stdout);
    printf(": Init: bad caller: width WAS %d(0x%x)\n", aISize, aISize);
  }
  if ((aBSize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aBSize) &&
      !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
    LineContainerFrame()->ListTag(stdout);
    printf(": Init: bad caller: height WAS %d(0x%x)\n", aBSize, aBSize);
  }
#endif
#ifdef NOISY_REFLOW
  LineContainerFrame()->ListTag(stdout);
  printf(": BeginLineReflow: %d,%d,%d,%d impacted=%s %s\n", aICoord, aBCoord,
         aISize, aBSize, aImpactedByFloats ? "true" : "false",
         aIsTopOfPage ? "top-of-page" : "");
#endif
#ifdef DEBUG
  mSpansAllocated = mSpansFreed = mFramesAllocated = mFramesFreed = 0;
#endif

  mFirstLetterStyleOK = false;
  mIsTopOfPage = aIsTopOfPage;
  mImpactedByFloats = aImpactedByFloats;
  mTotalPlacedFrames = 0;
  if (!mBaseLineLayout) {
    mLineIsEmpty = true;
    mLineAtStart = true;
  } else {
    mLineIsEmpty = false;
    mLineAtStart = false;
  }
  mLineEndsInBR = false;
  mSpanDepth = 0;
  mMaxStartBoxBSize = mMaxEndBoxBSize = 0;

  if (mGotLineBox) {
    mLineBox->ClearHasMarker();
  }

  PerSpanData* psd = NewPerSpanData();
  mCurrentSpan = mRootSpan = psd;
  psd->mReflowInput = &mLineContainerRI;
  psd->mIStart = aICoord;
  psd->mICoord = aICoord;
  psd->mIEnd = aICoord + aISize;
  // Set up inset to be used for text-wrap:balance implementation, but only if
  // the available size is greater than inset.
  psd->mInset = aISize > aInset ? aInset : 0;
  mContainerSize = aContainerSize;

  mBStartEdge = aBCoord;

  psd->mNoWrap = !mStyleText->WhiteSpaceCanWrapStyle() || mSuppressLineWrap;
  psd->mWritingMode = aWritingMode;

  // Determine if this is the first line of the block (or first after a hard
  // line-break, if `each-line` is in effect).
  nsIFrame* containerFrame = LineContainerFrame();
  if (!containerFrame->IsRubyTextContainerFrame()) {
    bool isFirstLineOrAfterHardBreak = [&] {
      if (mLineNumber > 0) {
        return mStyleText->mTextIndent.each_line && GetLine() &&
               !GetLine()->prev()->IsLineWrapped();
      }
      if (nsBlockFrame* prevBlock =
              do_QueryFrame(containerFrame->GetPrevInFlow())) {
        return mStyleText->mTextIndent.each_line &&
               (prevBlock->Lines().empty() ||
                !prevBlock->LinesEnd().prev()->IsLineWrapped());
      }
      return true;
    }();

    // Resolve and apply the text-indent value if this line requires it.
    // The `hanging` option inverts which lines are to be indented.
    if (isFirstLineOrAfterHardBreak != mStyleText->mTextIndent.hanging) {
      nscoord pctBasis = mLineContainerRI.ComputedISize();
      mTextIndent = mStyleText->mTextIndent.length.Resolve(pctBasis);
      psd->mICoord += mTextIndent;
    }
  }

  PerFrameData* pfd = NewPerFrameData(containerFrame);
  pfd->mAscent = 0;
  pfd->mSpan = psd;
  psd->mFrame = pfd;
  if (containerFrame->IsRubyTextContainerFrame()) {
    // Ruby text container won't be reflowed via ReflowFrame, hence the
    // relative positioning information should be recorded here.
    MOZ_ASSERT(mBaseLineLayout != this);
    pfd->mIsRelativelyOrStickyPos =
        mLineContainerRI.mStyleDisplay->IsRelativelyOrStickyPositionedStyle();
    if (pfd->mIsRelativelyOrStickyPos) {
      MOZ_ASSERT(mLineContainerRI.GetWritingMode() == pfd->mWritingMode,
                 "mLineContainerRI.frame == frame, "
                 "hence they should have identical writing mode");
      pfd->mOffsets =
          mLineContainerRI.ComputedLogicalOffsets(pfd->mWritingMode);
    }
  }
}

bool nsLineLayout::EndLineReflow() {
#ifdef NOISY_REFLOW
  LineContainerFrame()->ListTag(stdout);
  printf(": EndLineReflow: width=%d\n",
         mRootSpan->mICoord - mRootSpan->mIStart);
#endif

  NS_ASSERTION(!mBaseLineLayout ||
                   (!mSpansAllocated && !mSpansFreed && !mSpanFreeList &&
                    !mFramesAllocated && !mFramesFreed && !mFrameFreeList),
               "Allocated frames or spans on non-base line layout?");
  MOZ_ASSERT(mRootSpan == mCurrentSpan);

  UnlinkFrame(mRootSpan->mFrame);
  mCurrentSpan = mRootSpan = nullptr;

  NS_ASSERTION(mSpansAllocated == mSpansFreed, "leak");
  NS_ASSERTION(mFramesAllocated == mFramesFreed, "leak");

#if 0
  static int32_t maxSpansAllocated = NS_LINELAYOUT_NUM_SPANS;
  static int32_t maxFramesAllocated = NS_LINELAYOUT_NUM_FRAMES;
  if (mSpansAllocated > maxSpansAllocated) {
    printf("XXX: saw a line with %d spans\n", mSpansAllocated);
    maxSpansAllocated = mSpansAllocated;
  }
  if (mFramesAllocated > maxFramesAllocated) {
    printf("XXX: saw a line with %d frames\n", mFramesAllocated);
    maxFramesAllocated = mFramesAllocated;
  }
#endif

  return mUsedOverflowWrap;
}

// XXX swtich to a single mAvailLineWidth that we adjust as each frame
// on the line is placed. Each span can still have a per-span mICoord that
// tracks where a child frame is going in its span; they don't need a
// per-span mIStart?

void nsLineLayout::UpdateBand(WritingMode aWM,
                              const LogicalRect& aNewAvailSpace,
                              nsIFrame* aFloatFrame) {
  WritingMode lineWM = mRootSpan->mWritingMode;
  // need to convert to our writing mode, because we might have a different
  // mode from the caller due to dir: auto
  LogicalRect availSpace =
      aNewAvailSpace.ConvertTo(lineWM, aWM, ContainerSize());
#ifdef REALLY_NOISY_REFLOW
  printf(
      "nsLL::UpdateBand %d, %d, %d, %d, (converted to %d, %d, %d, %d); "
      "frame=%p\n will set mImpacted to true\n",
      aNewAvailSpace.IStart(aWM), aNewAvailSpace.BStart(aWM),
      aNewAvailSpace.ISize(aWM), aNewAvailSpace.BSize(aWM),
      availSpace.IStart(lineWM), availSpace.BStart(lineWM),
      availSpace.ISize(lineWM), availSpace.BSize(lineWM), aFloatFrame);
#endif
#ifdef DEBUG
  if ((availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE) &&
      ABSURD_SIZE(availSpace.ISize(lineWM)) &&
      !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
    LineContainerFrame()->ListTag(stdout);
    printf(": UpdateBand: bad caller: ISize WAS %d(0x%x)\n",
           availSpace.ISize(lineWM), availSpace.ISize(lineWM));
  }
  if ((availSpace.BSize(lineWM) != NS_UNCONSTRAINEDSIZE) &&
      ABSURD_SIZE(availSpace.BSize(lineWM)) &&
      !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
    LineContainerFrame()->ListTag(stdout);
    printf(": UpdateBand: bad caller: BSize WAS %d(0x%x)\n",
           availSpace.BSize(lineWM), availSpace.BSize(lineWM));
  }
#endif

  // Compute the difference between last times width and the new width
  NS_WARNING_ASSERTION(
      mRootSpan->mIEnd != NS_UNCONSTRAINEDSIZE &&
          availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE,
      "have unconstrained inline size; this should only result from very large "
      "sizes, not attempts at intrinsic width calculation");
  // The root span's mIStart moves to aICoord
  nscoord deltaICoord = availSpace.IStart(lineWM) - mRootSpan->mIStart;
  // The inline size of all spans changes by this much (the root span's
  // mIEnd moves to aICoord + aISize, its new inline size is aISize)
  nscoord deltaISize =
      availSpace.ISize(lineWM) - (mRootSpan->mIEnd - mRootSpan->mIStart);
#ifdef NOISY_REFLOW
  LineContainerFrame()->ListTag(stdout);
  printf(": UpdateBand: %d,%d,%d,%d deltaISize=%d deltaICoord=%d\n",
         availSpace.IStart(lineWM), availSpace.BStart(lineWM),
         availSpace.ISize(lineWM), availSpace.BSize(lineWM), deltaISize,
         deltaICoord);
#endif

  // Update the root span position
  mRootSpan->mIStart += deltaICoord;
  mRootSpan->mIEnd += deltaICoord;
  mRootSpan->mICoord += deltaICoord;

  // Now update the right edges of the open spans to account for any
  // change in available space width
  for (PerSpanData* psd = mCurrentSpan; psd; psd = psd->mParent) {
    psd->mIEnd += deltaISize;
    psd->mContainsFloat = true;
#ifdef NOISY_REFLOW
    printf(" span %p: oldIEnd=%d newIEnd=%d\n", psd, psd->mIEnd - deltaISize,
           psd->mIEnd);
#endif
  }
  NS_ASSERTION(mRootSpan->mContainsFloat &&
                   mRootSpan->mIStart == availSpace.IStart(lineWM) &&
                   mRootSpan->mIEnd == availSpace.IEnd(lineWM),
               "root span was updated incorrectly?");

  // Update frame bounds
  // Note: Only adjust the outermost frames (the ones that are direct
  // children of the block), not the ones in the child spans. The reason
  // is simple: the frames in the spans have coordinates local to their
  // parent therefore they are moved when their parent span is moved.
  if (deltaICoord != 0) {
    for (PerFrameData* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
      pfd->mBounds.IStart(lineWM) += deltaICoord;
    }
  }

  mBStartEdge = availSpace.BStart(lineWM);
  mImpactedByFloats = true;

  mLastFloatWasLetterFrame = aFloatFrame->IsLetterFrame();
}

nsLineLayout::PerSpanData* nsLineLayout::NewPerSpanData() {
  nsLineLayout* outerLineLayout = GetOutermostLineLayout();
  PerSpanData* psd = outerLineLayout->mSpanFreeList;
  if (!psd) {
    void* mem = outerLineLayout->mArena.Allocate(sizeof(PerSpanData));
    psd = reinterpret_cast<PerSpanData*>(mem);
  } else {
    outerLineLayout->mSpanFreeList = psd->mNextFreeSpan;
  }
  psd->mParent = nullptr;
  psd->mFrame = nullptr;
  psd->mFirstFrame = nullptr;
  psd->mLastFrame = nullptr;
  psd->mReflowInput = nullptr;
  psd->mContainsFloat = false;
  psd->mHasNonemptyContent = false;
  psd->mBaseline = nullptr;

#ifdef DEBUG
  outerLineLayout->mSpansAllocated++;
#endif
  return psd;
}

void nsLineLayout::BeginSpan(nsIFrame* aFrame,
                             const ReflowInput* aSpanReflowInput,
                             nscoord aIStart, nscoord aIEnd,
                             nscoord* aBaseline) {
  NS_ASSERTION(aIEnd != NS_UNCONSTRAINEDSIZE,
               "should no longer be using unconstrained sizes");
#ifdef NOISY_REFLOW
  nsIFrame::IndentBy(stdout, mSpanDepth + 1);
  aFrame->ListTag(stdout);
  printf(": BeginSpan leftEdge=%d rightEdge=%d\n", aIStart, aIEnd);
#endif

  PerSpanData* psd = NewPerSpanData();
  // Link up span frame's pfd to point to its child span data
  PerFrameData* pfd = mCurrentSpan->mLastFrame;
  NS_ASSERTION(pfd->mFrame == aFrame, "huh?");
  pfd->mSpan = psd;

  // Init new span
  psd->mFrame = pfd;
  psd->mParent = mCurrentSpan;
  psd->mReflowInput = aSpanReflowInput;
  psd->mIStart = aIStart;
  psd->mICoord = aIStart;
  psd->mIEnd = aIEnd;
  psd->mInset = 0;  // inset applies only to the root span
  psd->mBaseline = aBaseline;

  nsIFrame* frame = aSpanReflowInput->mFrame;
  psd->mNoWrap = !frame->StyleText()->WhiteSpaceCanWrap(frame) ||
                 mSuppressLineWrap || frame->Style()->ShouldSuppressLineBreak();
  psd->mWritingMode = aSpanReflowInput->GetWritingMode();

  // Switch to new span
  mCurrentSpan = psd;
  mSpanDepth++;
}

nscoord nsLineLayout::EndSpan(nsIFrame* aFrame) {
  NS_ASSERTION(mSpanDepth > 0, "end-span without begin-span");
#ifdef NOISY_REFLOW
  nsIFrame::IndentBy(stdout, mSpanDepth);
  aFrame->ListTag(stdout);
  printf(": EndSpan width=%d\n", mCurrentSpan->mICoord - mCurrentSpan->mIStart);
#endif
  PerSpanData* psd = mCurrentSpan;
  MOZ_ASSERT(psd->mParent, "We never call this on the root");

  if (psd->mNoWrap && !psd->mParent->mNoWrap) {
    FlushNoWrapFloats();
  }

  nscoord iSizeResult = psd->mLastFrame ? (psd->mICoord - psd->mIStart) : 0;

  mSpanDepth--;
  mCurrentSpan->mReflowInput = nullptr;  // no longer valid so null it out!
  mCurrentSpan = mCurrentSpan->mParent;
  return iSizeResult;
}

void nsLineLayout::AttachFrameToBaseLineLayout(PerFrameData* aFrame) {
  MOZ_ASSERT(mBaseLineLayout,
             "This method must not be called in a base line layout.");

  PerFrameData* baseFrame = mBaseLineLayout->LastFrame();
  MOZ_ASSERT(aFrame && baseFrame);
  MOZ_ASSERT(!aFrame->mIsLinkedToBase,
             "The frame must not have been linked with the base");
#ifdef DEBUG
  LayoutFrameType baseType = baseFrame->mFrame->Type();
  LayoutFrameType annotationType = aFrame->mFrame->Type();
  MOZ_ASSERT((baseType == LayoutFrameType::RubyBaseContainer &&
              annotationType == LayoutFrameType::RubyTextContainer) ||
             (baseType == LayoutFrameType::RubyBase &&
              annotationType == LayoutFrameType::RubyText));
#endif

  aFrame->mNextAnnotation = baseFrame->mNextAnnotation;
  baseFrame->mNextAnnotation = aFrame;
  aFrame->mIsLinkedToBase = true;
}

int32_t nsLineLayout::GetCurrentSpanCount() const {
  MOZ_ASSERT(mCurrentSpan == mRootSpan, "bad linelayout user");
  int32_t count = 0;
  for (const auto* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
    count++;
  }
  return count;
}

void nsLineLayout::SplitLineTo(int32_t aNewCount) {
  MOZ_ASSERT(mCurrentSpan == mRootSpan, "bad linelayout user");

#ifdef REALLY_NOISY_PUSHING
  printf("SplitLineTo %d (current count=%d); before:\n", aNewCount,
         GetCurrentSpanCount());
  DumpPerSpanData(mRootSpan, 1);
#endif
  for (auto* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
    if (--aNewCount == 0) {
      // Truncate list at pfd (we keep pfd, but anything following is freed)
      PerFrameData* next = pfd->mNext;
      pfd->mNext = nullptr;
      mRootSpan->mLastFrame = pfd;

      // Now unlink all of the frames following pfd
      UnlinkFrame(next);
      break;
    }
  }
#ifdef NOISY_PUSHING
  printf("SplitLineTo %d (current count=%d); after:\n", aNewCount,
         GetCurrentSpanCount());
  DumpPerSpanData(mRootSpan, 1);
#endif
}

void nsLineLayout::PushFrame(nsIFrame* aFrame) {
  PerSpanData* psd = mCurrentSpan;
  NS_ASSERTION(psd->mLastFrame->mFrame == aFrame, "pushing non-last frame");

#ifdef REALLY_NOISY_PUSHING
  nsIFrame::IndentBy(stdout, mSpanDepth);
  printf("PushFrame %p, before:\n", psd);
  DumpPerSpanData(psd, 1);
#endif

  // Take the last frame off of the span's frame list
  PerFrameData* pfd = psd->mLastFrame;
  if (pfd == psd->mFirstFrame) {
    // We are pushing away the only frame...empty the list
    psd->mFirstFrame = nullptr;
    psd->mLastFrame = nullptr;
  } else {
    PerFrameData* prevFrame = pfd->mPrev;
    prevFrame->mNext = nullptr;
    psd->mLastFrame = prevFrame;
  }

  // Now unlink the frame
  MOZ_ASSERT(!pfd->mNext);
  UnlinkFrame(pfd);
#ifdef NOISY_PUSHING
  nsIFrame::IndentBy(stdout, mSpanDepth);
  printf("PushFrame: %p after:\n", psd);
  DumpPerSpanData(psd, 1);
#endif
}

void nsLineLayout::UnlinkFrame(PerFrameData* pfd) {
  while (nullptr != pfd) {
    PerFrameData* next = pfd->mNext;
    if (pfd->mIsLinkedToBase) {
      // This frame is linked to a ruby base, and should not be freed
      // now. Just unlink it from the span. It will be freed when its
      // base frame gets unlinked.
      pfd->mNext = pfd->mPrev = nullptr;
      pfd = next;
      continue;
    }

    // It is a ruby base frame. If there are any annotations
    // linked to this frame, free them first.
    PerFrameData* annotationPFD = pfd->mNextAnnotation;
    while (annotationPFD) {
      PerFrameData* nextAnnotation = annotationPFD->mNextAnnotation;
      MOZ_ASSERT(
          annotationPFD->mNext == nullptr && annotationPFD->mPrev == nullptr,
          "PFD in annotations should have been unlinked.");
      FreeFrame(annotationPFD);
      annotationPFD = nextAnnotation;
    }

    FreeFrame(pfd);
    pfd = next;
  }
}

void nsLineLayout::FreeFrame(PerFrameData* pfd) {
  if (nullptr != pfd->mSpan) {
    FreeSpan(pfd->mSpan);
  }
  nsLineLayout* outerLineLayout = GetOutermostLineLayout();
  pfd->mNext = outerLineLayout->mFrameFreeList;
  outerLineLayout->mFrameFreeList = pfd;
#ifdef DEBUG
  outerLineLayout->mFramesFreed++;
#endif
}

void nsLineLayout::FreeSpan(PerSpanData* psd) {
  // Unlink its frames
  UnlinkFrame(psd->mFirstFrame);

  nsLineLayout* outerLineLayout = GetOutermostLineLayout();
  // Now put the span on the free list since it's free too
  psd->mNextFreeSpan = outerLineLayout->mSpanFreeList;
  outerLineLayout->mSpanFreeList = psd;
#ifdef DEBUG
  outerLineLayout->mSpansFreed++;
#endif
}

bool nsLineLayout::IsZeroBSize() const {
  for (const auto* pfd = mCurrentSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
    if (0 != pfd->mBounds.BSize(mCurrentSpan->mWritingMode)) {
      return false;
    }
  }
  return true;
}

nsLineLayout::PerFrameData* nsLineLayout::NewPerFrameData(nsIFrame* aFrame) {
  nsLineLayout* outerLineLayout = GetOutermostLineLayout();
  PerFrameData* pfd = outerLineLayout->mFrameFreeList;
  if (!pfd) {
    void* mem = outerLineLayout->mArena.Allocate(sizeof(PerFrameData));
    pfd = reinterpret_cast<PerFrameData*>(mem);
  } else {
    outerLineLayout->mFrameFreeList = pfd->mNext;
  }
  pfd->mSpan = nullptr;
  pfd->mNext = nullptr;
  pfd->mPrev = nullptr;
  pfd->mNextAnnotation = nullptr;
  pfd->mFrame = aFrame;

  // all flags default to false
  pfd->mIsRelativelyOrStickyPos = false;
  pfd->mIsTextFrame = false;
  pfd->mIsNonEmptyTextFrame = false;
  pfd->mIsNonWhitespaceTextFrame = false;
  pfd->mIsLetterFrame = false;
  pfd->mRecomputeOverflow = false;
  pfd->mIsMarker = false;
  pfd->mSkipWhenTrimmingWhitespace = false;
  pfd->mIsEmpty = false;
  pfd->mIsPlaceholder = false;
  pfd->mIsLinkedToBase = false;

  pfd->mWritingMode = aFrame->GetWritingMode();
  WritingMode lineWM = mRootSpan->mWritingMode;
  pfd->mBounds = LogicalRect(lineWM);
  pfd->mOverflowAreas.Clear();
  pfd->mMargin = LogicalMargin(lineWM);
  pfd->mBorderPadding = LogicalMargin(lineWM);
  pfd->mOffsets = LogicalMargin(pfd->mWritingMode);

  pfd->mJustificationInfo = JustificationInfo();
  pfd->mJustificationAssignment = JustificationAssignment();

#ifdef DEBUG
  pfd->mBlockDirAlign = 0xFF;
  outerLineLayout->mFramesAllocated++;
#endif
  return pfd;
}

// Checks all four sides for percentage units.  This means it should
// only be used for things (margin, padding) where percentages on top
// and bottom depend on the *width* just like percentages on left and
// right.
template <typename T>
static bool HasPercentageUnitSide(const StyleRect<T>& aSides) {
  return aSides.Any([](const auto& aLength) { return aLength.HasPercent(); });
}

static bool HasPercentageUnitMargin(const nsStyleMargin& aStyleMargin) {
  for (const auto side : AllPhysicalSides()) {
    if (aStyleMargin.GetMargin(side).HasPercent()) {
      return true;
    }
  }
  return false;
}

static bool IsPercentageAware(const nsIFrame* aFrame, WritingMode aWM) {
  MOZ_ASSERT(aFrame, "null frame is not allowed");

  LayoutFrameType fType = aFrame->Type();
  if (fType == LayoutFrameType::Text) {
    // None of these things can ever be true for text frames.
    return false;
  }

  // Some of these things don't apply to non-replaced inline frames
  // (that is, fType == LayoutFrameType::Inline), but we won't bother making
  // things unnecessarily complicated, since they'll probably be set
  // quite rarely.

  const nsStyleMargin* margin = aFrame->StyleMargin();
  if (HasPercentageUnitMargin(*margin)) {
    return true;
  }

  const nsStylePadding* padding = aFrame->StylePadding();
  if (HasPercentageUnitSide(padding->mPadding)) {
    return true;
  }

  // Note that borders can't be aware of percentages

  const nsStylePosition* pos = aFrame->StylePosition();

  if ((pos->ISizeDependsOnContainer(aWM) && !pos->ISize(aWM).IsAuto()) ||
      pos->MaxISizeDependsOnContainer(aWM) ||
      pos->MinISizeDependsOnContainer(aWM) ||
      pos->mOffset.Get(LogicalSide::IStart, aWM).MaybePercentageAware() ||
      pos->mOffset.Get(LogicalSide::IEnd, aWM).MaybePercentageAware()) {
    return true;
  }

  if (pos->ISize(aWM).IsAuto()) {
    // We need to check for frames that shrink-wrap when they're auto
    // width.
    const nsStyleDisplay* disp = aFrame->StyleDisplay();
    if ((disp->DisplayOutside() == StyleDisplayOutside::Inline &&
         (disp->DisplayInside() == StyleDisplayInside::FlowRoot ||
          disp->DisplayInside() == StyleDisplayInside::Table)) ||
        fType == LayoutFrameType::HTMLButtonControl ||
        fType == LayoutFrameType::GfxButtonControl ||
        fType == LayoutFrameType::FieldSet) {
      return true;
    }

    // Per CSS 2.1, section 10.3.2:
    //   If 'height' and 'width' both have computed values of 'auto' and
    //   the element has an intrinsic ratio but no intrinsic height or
    //   width and the containing block's width does not itself depend
    //   on the replaced element's width, then the used value of 'width'
    //   is calculated from the constraint equation used for
    //   block-level, non-replaced elements in normal flow.
    nsIFrame* f = const_cast<nsIFrame*>(aFrame);
    if (f->GetAspectRatio() &&
        // Some percents are treated like 'auto', so check != coord
        !pos->BSize(aWM).ConvertsToLength()) {
      const IntrinsicSize& intrinsicSize = f->GetIntrinsicSize();
      if (!intrinsicSize.width && !intrinsicSize.height) {
        return true;
      }
    }
  }

  return false;
}

void nsLineLayout::ReflowFrame(nsIFrame* aFrame, nsReflowStatus& aReflowStatus,
                               ReflowOutput* aMetrics, bool& aPushedFrame) {
  // Initialize OUT parameter
  aPushedFrame = false;

  PerFrameData* pfd = NewPerFrameData(aFrame);
  PerSpanData* psd = mCurrentSpan;
  psd->AppendFrame(pfd);

#ifdef REALLY_NOISY_REFLOW
  nsIFrame::IndentBy(stdout, mSpanDepth);
  printf("%p: Begin ReflowFrame pfd=%p ", psd, pfd);
  aFrame->ListTag(stdout);
  printf("\n");
#endif

  if (mCurrentSpan == mRootSpan) {
    pfd->mFrame->RemoveProperty(nsIFrame::LineBaselineOffset());
  } else {
#ifdef DEBUG
    bool hasLineOffset;
    pfd->mFrame->GetProperty(nsIFrame::LineBaselineOffset(), &hasLineOffset);
    NS_ASSERTION(!hasLineOffset,
                 "LineBaselineOffset was set but was not expected");
#endif
  }

  mJustificationInfo = JustificationInfo();

  // Stash copies of some of the computed state away for later
  // (block-direction alignment, for example)
  WritingMode frameWM = pfd->mWritingMode;
  WritingMode lineWM = mRootSpan->mWritingMode;

  // NOTE: While the inline direction coordinate remains relative to the
  // parent span, the block direction coordinate is fixed at the top
  // edge for the line. During VerticalAlignFrames we will repair this
  // so that the block direction coordinate is properly set and relative
  // to the appropriate span.
  pfd->mBounds.IStart(lineWM) = psd->mICoord;
  pfd->mBounds.BStart(lineWM) = mBStartEdge;

  // We want to guarantee that we always make progress when
  // formatting. Therefore, if the object being placed on the line is
  // too big for the line, but it is the only thing on the line and is not
  // impacted by a float, then we go ahead and place it anyway. (If the line
  // is impacted by one or more floats, then it is safe to break because
  // we can move the line down below float(s).)
  //
  // Capture this state *before* we reflow the frame in case it clears
  // the state out. We need to know how to treat the current frame
  // when breaking.
  bool notSafeToBreak = LineIsEmpty() && !mImpactedByFloats;

  // Figure out whether we're talking about a textframe here
  LayoutFrameType frameType = aFrame->Type();
  const bool isText = frameType == LayoutFrameType::Text;

  // Inline-ish and text-ish things don't compute their width;
  // everything else does.  We need to give them an available width that
  // reflects the space left on the line.
  LAYOUT_WARN_IF_FALSE(psd->mIEnd != NS_UNCONSTRAINEDSIZE,
                       "have unconstrained width; this should only result from "
                       "very large sizes, not attempts at intrinsic width "
                       "calculation");
  nscoord availableSpaceOnLine = psd->mIEnd - psd->mICoord - psd->mInset;

  // Setup reflow input for reflowing the frame
  Maybe<ReflowInput> reflowInputHolder;
  if (!isText) {
    // Compute the available size for the frame. This available width
    // includes room for the side margins.
    // For now, set the available block-size to unconstrained always.
    LogicalSize availSize = mLineContainerRI.ComputedSize(frameWM);
    availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
    reflowInputHolder.emplace(mPresContext, *psd->mReflowInput, aFrame,
                              availSize);
    ReflowInput& reflowInput = *reflowInputHolder;
    reflowInput.mLineLayout = this;
    reflowInput.mFlags.mIsTopOfPage = mIsTopOfPage;
    if (reflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE) {
      reflowInput.SetAvailableISize(availableSpaceOnLine);
    }
    pfd->mMargin = reflowInput.ComputedLogicalMargin(lineWM);
    pfd->mBorderPadding = reflowInput.ComputedLogicalBorderPadding(lineWM);
    pfd->mIsRelativelyOrStickyPos =
        reflowInput.mStyleDisplay->IsRelativelyOrStickyPositionedStyle();
    if (pfd->mIsRelativelyOrStickyPos) {
      pfd->mOffsets = reflowInput.ComputedLogicalOffsets(frameWM);
    }

    // Calculate whether the the frame should have a start margin and
    // subtract the margin from the available width if necessary.
    // The margin will be applied to the starting inline coordinates of
    // the frame in CanPlaceFrame() after reflowing the frame.
    AllowForStartMargin(pfd, reflowInput);
  }
  // if isText(), no need to propagate NS_FRAME_IS_DIRTY from the parent,
  // because reflow doesn't look at the dirty bits on the frame being reflowed.

  // See if this frame depends on the inline-size of its containing block.
  // If so, disable resize reflow optimizations for the line.  (Note that,
  // to be conservative, we do this if we *try* to fit a frame on a
  // line, even if we don't succeed.)  (Note also that we can only make
  // this IsPercentageAware check *after* we've constructed our
  // ReflowInput, because that construction may be what forces aFrame
  // to lazily initialize its (possibly-percent-valued) intrinsic size.)
  if (mGotLineBox && IsPercentageAware(aFrame, lineWM)) {
    mLineBox->DisableResizeReflowOptimization();
  }

  // Note that we don't bother positioning the frame yet, because we're probably
  // going to end up moving it when we do the block-direction alignment.

  // Adjust float manager coordinate system for the frame.
  ReflowOutput reflowOutput(lineWM);
#ifdef DEBUG
  reflowOutput.ISize(lineWM) = nscoord(0xdeadbeef);
  reflowOutput.BSize(lineWM) = nscoord(0xdeadbeef);
#endif
  nscoord tI = pfd->mBounds.LineLeft(lineWM, ContainerSize());
  nscoord tB = pfd->mBounds.BStart(lineWM);
  mFloatManager->Translate(tI, tB);

  int32_t savedOptionalBreakOffset;
  gfxBreakPriority savedOptionalBreakPriority;
  nsIFrame* savedOptionalBreakFrame = GetLastOptionalBreakPosition(
      &savedOptionalBreakOffset, &savedOptionalBreakPriority);

  if (!isText) {
    aFrame->Reflow(mPresContext, reflowOutput, *reflowInputHolder,
                   aReflowStatus);
  } else {
    static_cast<nsTextFrame*>(aFrame)->ReflowText(
        *this, availableSpaceOnLine,
        psd->mReflowInput->mRenderingContext->GetDrawTarget(), reflowOutput,
        aReflowStatus);
  }

  pfd->mJustificationInfo = mJustificationInfo;
  mJustificationInfo = JustificationInfo();

  // See if the frame is a placeholderFrame and if it is process
  // the float. At the same time, check if the frame has any non-collapsed-away
  // content.
  bool placedFloat = false;
  bool isEmpty;
  if (frameType == LayoutFrameType::None) {
    isEmpty = pfd->mFrame->IsEmpty();
  } else if (LayoutFrameType::Placeholder == frameType) {
    isEmpty = true;
    pfd->mIsPlaceholder = true;
    pfd->mSkipWhenTrimmingWhitespace = true;
    nsIFrame* outOfFlowFrame = nsLayoutUtils::GetFloatFromPlaceholder(aFrame);
    if (outOfFlowFrame) {
      if (psd->mNoWrap &&
          // We can always place floats in an empty line.
          !LineIsEmpty() &&
          // We always place floating letter frames. This kinda sucks. They'd
          // usually fall into the LineIsEmpty() check anyway, except when
          // there's something like a ::marker before or what not. We actually
          // need to place them now, because they're pretty nasty and they
          // create continuations that are in flow and not a kid of the
          // previous continuation's parent. We don't want the deferred reflow
          // of the letter frame to kill a continuation after we've stored it
          // in the line layout data structures. See bug 1490281 to fix the
          // underlying issue. When that's fixed this check should be removed.
          !outOfFlowFrame->IsLetterFrame() &&
          !GetOutermostLineLayout()->mBlockRS->mFlags.mCanHaveOverflowMarkers) {
        // We'll do this at the next break opportunity.
        RecordNoWrapFloat(outOfFlowFrame);
      } else {
        placedFloat = TryToPlaceFloat(outOfFlowFrame);
      }
    }
  } else if (isText) {
    // Note non-empty text-frames for inline frame compatibility hackery
    pfd->mIsTextFrame = true;
    auto* textFrame = static_cast<nsTextFrame*>(pfd->mFrame);
    isEmpty = !textFrame->HasNoncollapsedCharacters();
    if (!isEmpty) {
      pfd->mIsNonEmptyTextFrame = true;
      pfd->mIsNonWhitespaceTextFrame =
          !textFrame->GetContent()->TextIsOnlyWhitespace();
    }
  } else if (LayoutFrameType::Br == frameType) {
    pfd->mSkipWhenTrimmingWhitespace = true;
    isEmpty = false;
  } else {
    if (LayoutFrameType::Letter == frameType) {
      pfd->mIsLetterFrame = true;
    }
    if (pfd->mSpan) {
      isEmpty = !pfd->mSpan->mHasNonemptyContent && pfd->mFrame->IsSelfEmpty();
    } else {
      isEmpty = pfd->mFrame->IsEmpty();
    }
  }
  pfd->mIsEmpty = isEmpty;

  mFloatManager->Translate(-tI, -tB);

  NS_ASSERTION(reflowOutput.ISize(lineWM) >= 0, "bad inline size");
  NS_ASSERTION(reflowOutput.BSize(lineWM) >= 0, "bad block size");
  if (reflowOutput.ISize(lineWM) < 0) {
    reflowOutput.ISize(lineWM) = 0;
  }
  if (reflowOutput.BSize(lineWM) < 0) {
    reflowOutput.BSize(lineWM) = 0;
  }

#ifdef DEBUG
  // Note: break-before means ignore the reflow metrics since the
  // frame will be reflowed another time.
  if (!aReflowStatus.IsInlineBreakBefore()) {
    if ((ABSURD_SIZE(reflowOutput.ISize(lineWM)) ||
         ABSURD_SIZE(reflowOutput.BSize(lineWM))) &&
        !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
      printf("nsLineLayout: ");
      aFrame->ListTag(stdout);
      printf(" metrics=%d,%d!\n", reflowOutput.Width(), reflowOutput.Height());
    }
    if ((reflowOutput.Width() == nscoord(0xdeadbeef)) ||
        (reflowOutput.Height() == nscoord(0xdeadbeef))) {
      printf("nsLineLayout: ");
      aFrame->ListTag(stdout);
      printf(" didn't set w/h %d,%d!\n", reflowOutput.Width(),
             reflowOutput.Height());
    }
  }
#endif

  // Unlike with non-inline reflow, the overflow area here does *not*
  // include the accumulation of the frame's bounds and its inline
  // descendants' bounds. Nor does it include the outline area; it's
  // just the union of the bounds of any absolute children. That is
  // added in later by nsLineLayout::ReflowInlineFrames.
  pfd->mOverflowAreas = reflowOutput.mOverflowAreas;

  pfd->mBounds.ISize(lineWM) = reflowOutput.ISize(lineWM);
  pfd->mBounds.BSize(lineWM) = reflowOutput.BSize(lineWM);

  // Size the frame, but |RelativePositionFrames| will size the view.
  aFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd));

  // Tell the frame that we're done reflowing it
  aFrame->DidReflow(mPresContext, isText ? nullptr : reflowInputHolder.ptr());

  if (aMetrics) {
    *aMetrics = reflowOutput;
  }

  if (!aReflowStatus.IsInlineBreakBefore()) {
    // If frame is complete and has a next-in-flow, we need to delete
    // them now. Do not do this when a break-before is signaled because
    // the frame is going to get reflowed again (and may end up wanting
    // a next-in-flow where it ends up).
    if (aReflowStatus.IsComplete()) {
      if (nsIFrame* kidNextInFlow = aFrame->GetNextInFlow()) {
        // Remove all of the childs next-in-flows. Make sure that we ask
        // the right parent to do the removal (it's possible that the
        // parent is not this because we are executing pullup code)
        FrameDestroyContext context(aFrame->PresShell());
        kidNextInFlow->GetParent()->DeleteNextInFlowChild(context,
                                                          kidNextInFlow, true);
      }
    }

    // Check whether this frame breaks up text runs. All frames break up text
    // runs (hence return false here) except for text frames and inline
    // containers.
    bool continuingTextRun = aFrame->CanContinueTextRun();

    // Clear any residual mTrimmableISize if this isn't a text frame
    if (!continuingTextRun && !pfd->mSkipWhenTrimmingWhitespace) {
      mTrimmableISize = 0;
    }

    // See if we can place the frame. If we can't fit it, then we
    // return now.
    bool optionalBreakAfterFits;
    NS_ASSERTION(isText || !reflowInputHolder->mStyleDisplay->IsFloating(
                               reflowInputHolder->mFrame),
                 "How'd we get a floated inline frame? "
                 "The frame ctor should've dealt with this.");
    if (CanPlaceFrame(pfd, notSafeToBreak, continuingTextRun,
                      savedOptionalBreakFrame != nullptr, reflowOutput,
                      aReflowStatus, &optionalBreakAfterFits)) {
      if (!isEmpty) {
        psd->mHasNonemptyContent = true;
        mLineIsEmpty = false;
        if (!pfd->mSpan) {
          // nonempty leaf content has been placed
          mLineAtStart = false;
        }
        if (LayoutFrameType::Ruby == frameType) {
          mHasRuby = true;
          SyncAnnotationBounds(pfd);
        }
      }

      // Place the frame, updating aBounds with the final size and
      // location.  Then apply the bottom+right margins (as
      // appropriate) to the frame.
      PlaceFrame(pfd, reflowOutput);
      PerSpanData* span = pfd->mSpan;
      if (span) {
        // The frame we just finished reflowing is an inline
        // container.  It needs its child frames aligned in the block direction,
        // so do most of it now.
        VerticalAlignFrames(span);
      }

      if (!continuingTextRun && !psd->mNoWrap) {
        if (!LineIsEmpty() || placedFloat) {
          // record soft break opportunity after this content that can't be
          // part of a text run. This is not a text frame so we know
          // that offset INT32_MAX means "after the content".
          if ((!aFrame->IsPlaceholderFrame() || LineIsEmpty()) &&
              NotifyOptionalBreakPosition(aFrame, INT32_MAX,
                                          optionalBreakAfterFits,
                                          gfxBreakPriority::eNormalBreak)) {
            // If this returns true then we are being told to actually break
            // here.
            aReflowStatus.SetInlineLineBreakAfter();
          }
        }
      }
    } else {
      PushFrame(aFrame);
      aPushedFrame = true;
      // Undo any saved break positions that the frame might have told us about,
      // since we didn't end up placing it
      RestoreSavedBreakPosition(savedOptionalBreakFrame,
                                savedOptionalBreakOffset,
                                savedOptionalBreakPriority);
    }
  } else {
    PushFrame(aFrame);
    aPushedFrame = true;
  }

#ifdef REALLY_NOISY_REFLOW
  nsIFrame::IndentBy(stdout, mSpanDepth);
  printf("End ReflowFrame ");
  aFrame->ListTag(stdout);
  printf(" status=%x\n", aReflowStatus);
#endif
}

void nsLineLayout::AllowForStartMargin(PerFrameData* pfd,
                                       ReflowInput& aReflowInput) {
  NS_ASSERTION(!aReflowInput.mStyleDisplay->IsFloating(aReflowInput.mFrame),
               "How'd we get a floated inline frame? "
               "The frame ctor should've dealt with this.");

  WritingMode lineWM = mRootSpan->mWritingMode;

  // Only apply start-margin on the first-in flow for inline frames,
  // and make sure to not apply it to any inline other than the first
  // in an ib split.  Note that the ib sibling (block-in-inline
  // sibling) annotations only live on the first continuation, but we
  // don't want to apply the start margin for later continuations
  // anyway.  For box-decoration-break:clone we apply the start-margin
  // on all continuations.
  if ((pfd->mFrame->GetPrevContinuation() ||
       pfd->mFrame->FrameIsNonFirstInIBSplit()) &&
      aReflowInput.mStyleBorder->mBoxDecorationBreak ==
          StyleBoxDecorationBreak::Slice) {
    // Zero this out so that when we compute the max-element-width of
    // the frame we will properly avoid adding in the starting margin.
    pfd->mMargin.IStart(lineWM) = 0;
  } else if (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedISize()) {
    NS_WARNING_ASSERTION(
        NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize(),
        "have unconstrained inline-size; this should only result from very "
        "large sizes, not attempts at intrinsic inline-size calculation");
    // For inline-ish and text-ish things (which don't compute widths
    // in the reflow input), adjust available inline-size to account
    // for the start margin. The end margin will be accounted for when
    // we finish flowing the frame.
    WritingMode wm = aReflowInput.GetWritingMode();
    aReflowInput.SetAvailableISize(
        aReflowInput.AvailableISize() -
        pfd->mMargin.ConvertTo(wm, lineWM).IStart(wm));
  }
}

nscoord nsLineLayout::GetCurrentFrameInlineDistanceFromBlock() {
  nscoord x = 0;
  for (const auto* psd = mCurrentSpan; psd; psd = psd->mParent) {
    x += psd->mICoord;
  }
  return x;
}

/**
 * This method syncs bounds of ruby annotations and ruby annotation
 * containers from their rect. It is necessary because:
 * Containers are not part of the line in their levels, which means
 * their bounds are not set properly before.
 * Ruby annotations' position may have been changed when reflowing
 * their containers.
 */

void nsLineLayout::SyncAnnotationBounds(PerFrameData* aRubyFrame) {
  MOZ_ASSERT(aRubyFrame->mFrame->IsRubyFrame());
  MOZ_ASSERT(aRubyFrame->mSpan);

  PerSpanData* span = aRubyFrame->mSpan;
  WritingMode lineWM = mRootSpan->mWritingMode;
  for (PerFrameData* pfd = span->mFirstFrame; pfd; pfd = pfd->mNext) {
    for (PerFrameData* rtc = pfd->mNextAnnotation; rtc;
         rtc = rtc->mNextAnnotation) {
      if (lineWM.IsOrthogonalTo(rtc->mFrame->GetWritingMode())) {
        // Inter-character case: don't attempt to sync annotation bounds.
        continue;
      }
      // When the annotation container is reflowed, the width of the
      // ruby container is unknown so we use a dummy container size;
      // in the case of RTL block direction, the final position will be
      // fixed up later.
      const nsSize dummyContainerSize;
      LogicalRect rtcBounds(lineWM, rtc->mFrame->GetRect(), dummyContainerSize);
      rtc->mBounds = rtcBounds;
      nsSize rtcSize = rtcBounds.Size(lineWM).GetPhysicalSize(lineWM);
      for (PerFrameData* rt = rtc->mSpan->mFirstFrame; rt; rt = rt->mNext) {
        LogicalRect rtBounds = rt->mFrame->GetLogicalRect(lineWM, rtcSize);
        MOZ_ASSERT(rt->mBounds.Size(lineWM) == rtBounds.Size(lineWM),
                   "Size of the annotation should not have been changed");
        rt->mBounds.SetOrigin(lineWM, rtBounds.Origin(lineWM));
      }
    }
  }
}

/**
 * See if the frame can be placed now that we know it's desired size.
 * We can always place the frame if the line is empty. Note that we
 * know that the reflow-status is not a break-before because if it was
 * ReflowFrame above would have returned false, preventing this method
 * from being called. The logic in this method assumes that.
 *
 * Note that there is no check against the Y coordinate because we
 * assume that the caller will take care of that.
 */

bool nsLineLayout::CanPlaceFrame(PerFrameData* pfd, bool aNotSafeToBreak,
                                 bool aFrameCanContinueTextRun,
                                 bool aCanRollBackBeforeFrame,
                                 ReflowOutput& aMetrics,
                                 nsReflowStatus& aStatus,
                                 bool* aOptionalBreakAfterFits) {
  MOZ_ASSERT(pfd && pfd->mFrame, "bad args, null pointers for frame data");

  *aOptionalBreakAfterFits = true;

  WritingMode lineWM = mRootSpan->mWritingMode;
  /*
   * We want to only apply the end margin if we're the last continuation and
   * either not in an {ib} split or the last inline in it.  In all other
   * cases we want to zero it out.  That means zeroing it out if any of these
   * conditions hold:
   * 1) The frame is not complete (in this case it will get a next-in-flow)
   * 2) The frame is complete but has a non-fluid continuation on its
   *    continuation chain.  Note that if it has a fluid continuation, that
   *    continuation will get destroyed later, so we don't want to drop the
   *    end-margin in that case.
   * 3) The frame is in an {ib} split and is not the last part.
   *
   * However, none of that applies if this is a letter frame (XXXbz why?)
   *
   * For box-decoration-break:clone we apply the end margin on all
   * continuations (that are not letter frames).
   */

  if ((aStatus.IsIncomplete() ||
       pfd->mFrame->LastInFlow()->GetNextContinuation() ||
       pfd->mFrame->FrameIsNonLastInIBSplit()) &&
      !pfd->mIsLetterFrame &&
      pfd->mFrame->StyleBorder()->mBoxDecorationBreak ==
          StyleBoxDecorationBreak::Slice) {
    pfd->mMargin.IEnd(lineWM) = 0;
  }

  // Apply the start margin to the frame bounds.
  nscoord startMargin = pfd->mMargin.IStart(lineWM);
  nscoord endMargin = pfd->mMargin.IEnd(lineWM);

  pfd->mBounds.IStart(lineWM) += startMargin;

  PerSpanData* psd = mCurrentSpan;
  if (psd->mNoWrap) {
    // When wrapping is off, everything fits.
    return true;
  }

#ifdef NOISY_CAN_PLACE_FRAME
  if (psd->mFrame) {
    psd->mFrame->mFrame->ListTag(stdout);
  }
  printf(": aNotSafeToBreak=%s frame=", aNotSafeToBreak ? "true" : "false");
  pfd->mFrame->ListTag(stdout);
  printf(" frameWidth=%d, margins=%d,%d\n",
         pfd->mBounds.IEnd(lineWM) + endMargin - psd->mICoord, startMargin,
         endMargin);
#endif

  // Set outside to true if the result of the reflow leads to the
  // frame sticking outside of our available area.
  bool outside =
      pfd->mBounds.IEnd(lineWM) - mTrimmableISize + endMargin > psd->mIEnd;
  if (!outside) {
    // If it fits, it fits
#ifdef NOISY_CAN_PLACE_FRAME
    printf(" ==> inside\n");
#endif
    return true;
  }
  *aOptionalBreakAfterFits = false;

  // When it doesn't fit, check for a few special conditions where we
  // allow it to fit anyway.
  if (0 == startMargin + pfd->mBounds.ISize(lineWM) + endMargin) {
    // Empty frames always fit right where they are
#ifdef NOISY_CAN_PLACE_FRAME
    printf(" ==> empty frame fits\n");
#endif
    return true;
  }

  // another special case:  always place a BR
  if (pfd->mFrame->IsBrFrame()) {
#ifdef NOISY_CAN_PLACE_FRAME
    printf(" ==> BR frame fits\n");
#endif
    return true;
  }

  if (aNotSafeToBreak) {
    // There are no frames on the line that take up width and the line is
    // not impacted by floats, so we must allow the current frame to be
    // placed on the line
#ifdef NOISY_CAN_PLACE_FRAME
    printf(" ==> not-safe and not-impacted fits: ");
    while (nullptr != psd) {
      printf(" ", psd, psd->mICoord, psd->mIStart);
      psd = psd->mParent;
    }
    printf("\n");
#endif
    return true;
  }

  // Special check for span frames
  if (pfd->mSpan && pfd->mSpan->mContainsFloat) {
    // If the span either directly or indirectly contains a float then
    // it fits. Why? It's kind of complicated, but here goes:
    //
    // 1. CanPlaceFrame is used for all frame placements on a line,
    // and in a span. This includes recursively placement of frames
    // inside of spans, and the span itself. Because the logic always
    // checks for room before proceeding (the code above here), the
    // only things on a line will be those things that "fit".
    //
    // 2. Before a float is placed on a line, the line has to be empty
    // (otherwise it's a "below current line" float and will be placed
    // after the line).
    //
    // Therefore, if the span directly or indirectly has a float
    // then it means that at the time of the placement of the float
    // the line was empty. Because of #1, only the frames that fit can
    // be added after that point, therefore we can assume that the
    // current span being placed has fit.
    //
    // So how do we get here and have a span that should already fit
    // and yet doesn't: Simple: span's that have the no-wrap attribute
    // set on them and contain a float and are placed where they
    // don't naturally fit.
    return true;
  }

  if (aFrameCanContinueTextRun) {
    // Let it fit, but we reserve the right to roll back.
    // Note that we usually won't get here because a text frame will break
    // itself to avoid exceeding the available width.
    // We'll only get here for text frames that couldn't break early enough.
#ifdef NOISY_CAN_PLACE_FRAME
    printf(" ==> placing overflowing textrun, requesting backup\n");
#endif

    // We will want to try backup.
    mNeedBackup = true;
    return true;
  }

#ifdef NOISY_CAN_PLACE_FRAME
  printf(" ==> didn't fit\n");
#endif
  aStatus.SetInlineLineBreakBeforeAndReset();
  return false;
}

/**
 * Place the frame. Update running counters.
 */

void nsLineLayout::PlaceFrame(PerFrameData* pfd, ReflowOutput& aMetrics) {
  WritingMode lineWM = mRootSpan->mWritingMode;

  // If the frame's block direction does not match the line's, we can't use
  // its ascent; instead, treat it as a block with baseline at the block-end
  // edge (or block-begin in the case of an "inverted" line).
  if (pfd->mWritingMode.GetBlockDir() != lineWM.GetBlockDir()) {
    pfd->mAscent = lineWM.IsAlphabeticalBaseline()
                       ? lineWM.IsLineInverted() ? 0 : aMetrics.BSize(lineWM)
                       : aMetrics.BSize(lineWM) / 2;
  } else {
    // For inline reflow participants, baseline may get assigned as the frame is
    // vertically aligned, which happens after this.
    const auto baselineSource = pfd->mFrame->StyleDisplay()->mBaselineSource;
    if (baselineSource == StyleBaselineSource::Auto ||
        pfd->mFrame->IsLineParticipant()) {
      if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
        pfd->mAscent = pfd->mFrame->GetLogicalBaseline(lineWM);
      } else {
        pfd->mAscent = aMetrics.BlockStartAscent();
      }
    } else {
      const auto sourceGroup = [baselineSource]() {
        switch (baselineSource) {
          case StyleBaselineSource::First:
            return BaselineSharingGroup::First;
          case StyleBaselineSource::Last:
            return BaselineSharingGroup::Last;
          case StyleBaselineSource::Auto:
            break;
        }
        MOZ_ASSERT_UNREACHABLE("Auto should be already handled?");
        return BaselineSharingGroup::First;
      }();
      // We ignore line-layout specific layout quirks by setting
      // `BaselineExportContext::Other`.
      // Note(dshin): For a lot of frames, the export context does not make a
      // difference, and we may be wasting the value cached in
      // `BlockStartAscent`.
      pfd->mAscent = pfd->mFrame->GetLogicalBaseline(
          lineWM, sourceGroup, BaselineExportContext::Other);
    }
  }

  // Advance to next inline coordinate
  mCurrentSpan->mICoord = pfd->mBounds.IEnd(lineWM) + pfd->mMargin.IEnd(lineWM);

  // Count the number of non-placeholder frames on the line...
  if (pfd->mFrame->IsPlaceholderFrame()) {
    NS_ASSERTION(
        pfd->mBounds.ISize(lineWM) == 0 && pfd->mBounds.BSize(lineWM) == 0,
        "placeholders should have 0 width/height (checking "
        "placeholders were never counted by the old code in "
        "this function)");
  } else {
    mTotalPlacedFrames++;
  }
}

void nsLineLayout::AddMarkerFrame(nsIFrame* aFrame,
                                  const ReflowOutput& aMetrics) {
  MOZ_ASSERT(mCurrentSpan == mRootSpan, "bad linelayout user");
  MOZ_ASSERT(mGotLineBox, "must have line box");

  nsBlockFrame* blockFrame = do_QueryFrame(LineContainerFrame());
  MOZ_ASSERT(blockFrame, "must be for block");
  if (!blockFrame->MarkerIsEmpty(aFrame)) {
    mLineIsEmpty = false;
    mHasMarker = true;
    mLineBox->SetHasMarker();
  }

  WritingMode lineWM = mRootSpan->mWritingMode;
  PerFrameData* pfd = NewPerFrameData(aFrame);
  PerSpanData* psd = mRootSpan;

  MOZ_ASSERT(psd->mFirstFrame, "adding marker to an empty line?");
  // Prepend the marker frame to the line.
  psd->mFirstFrame->mPrev = pfd;
  pfd->mNext = psd->mFirstFrame;
  psd->mFirstFrame = pfd;

  pfd->mIsMarker = true;
  if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
    pfd->mAscent = aFrame->GetLogicalBaseline(lineWM);
  } else {
    pfd->mAscent = aMetrics.BlockStartAscent();
  }

  // Note: block-coord value will be updated during block-direction alignment
  pfd->mBounds = LogicalRect(lineWM, aFrame->GetRect(), ContainerSize());
  pfd->mOverflowAreas = aMetrics.mOverflowAreas;
}

void nsLineLayout::RemoveMarkerFrame(nsIFrame* aFrame) {
  PerSpanData* psd = mCurrentSpan;
  MOZ_ASSERT(psd == mRootSpan, "::marker on non-root span?");
  MOZ_ASSERT(psd->mFirstFrame->mFrame == aFrame,
             "::marker is not the first frame?");
  PerFrameData* pfd = psd->mFirstFrame;
  MOZ_ASSERT(pfd != psd->mLastFrame, "::marker is the only frame?");
  pfd->mNext->mPrev = nullptr;
  psd->mFirstFrame = pfd->mNext;
  FreeFrame(pfd);
}

#ifdef DEBUG
void nsLineLayout::DumpPerSpanData(PerSpanData* psd, int32_t aIndent) {
  nsIFrame::IndentBy(stdout, aIndent);
  printf("%p: left=%d x=%d right=%d\n"static_cast<void*>(psd), psd->mIStart,
         psd->mICoord, psd->mIEnd);
  PerFrameData* pfd = psd->mFirstFrame;
  while (nullptr != pfd) {
    nsIFrame::IndentBy(stdout, aIndent + 1);
    pfd->mFrame->ListTag(stdout);
    nsRect rect =
        pfd->mBounds.GetPhysicalRect(psd->mWritingMode, ContainerSize());
    printf(" %d,%d,%d,%d\n", rect.x, rect.y, rect.width, rect.height);
    if (pfd->mSpan) {
      DumpPerSpanData(pfd->mSpan, aIndent + 1);
    }
    pfd = pfd->mNext;
  }
}
#endif

void nsLineLayout::RecordNoWrapFloat(nsIFrame* aFloat) {
  GetOutermostLineLayout()->mBlockRS->mNoWrapFloats.AppendElement(aFloat);
}

void nsLineLayout::FlushNoWrapFloats() {
  auto& noWrapFloats = GetOutermostLineLayout()->mBlockRS->mNoWrapFloats;
  for (nsIFrame* floatedFrame : noWrapFloats) {
    TryToPlaceFloat(floatedFrame);
  }
  noWrapFloats.Clear();
}

bool nsLineLayout::TryToPlaceFloat(nsIFrame* aFloat) {
  // Add mTrimmableISize to the available width since if the line ends here, the
  // width of the inline content will be reduced by mTrimmableISize.
  nscoord availableISize =
      mCurrentSpan->mIEnd - (mCurrentSpan->mICoord - mTrimmableISize);
  NS_ASSERTION(!(aFloat->IsLetterFrame() && GetFirstLetterStyleOK()),
               "FirstLetterStyle set on line with floating first letter");
  return GetOutermostLineLayout()->AddFloat(aFloat, availableISize);
}

bool nsLineLayout::NotifyOptionalBreakPosition(nsIFrame* aFrame,
                                               int32_t aOffset, bool aFits,
                                               gfxBreakPriority aPriority) {
  NS_ASSERTION(!aFits || !mNeedBackup,
               "Shouldn't be updating the break position with a break that fits"
               " after we've already flagged an overrun");
  MOZ_ASSERT(mCurrentSpan, "Should be doing line layout");
  if (mCurrentSpan->mNoWrap) {
    FlushNoWrapFloats();
  }

  // Remember the last break position that fits; if there was no break that fit,
  // just remember the first break
  if ((aFits && aPriority >= mLastOptionalBreakPriority) ||
      !mLastOptionalBreakFrame) {
    mLastOptionalBreakFrame = aFrame;
    mLastOptionalBreakFrameOffset = aOffset;
    mLastOptionalBreakPriority = aPriority;
  }
  return aFrame && mForceBreakFrame == aFrame &&
         mForceBreakFrameOffset == aOffset;
}

#define VALIGN_OTHER 0
#define VALIGN_TOP 1
#define VALIGN_BOTTOM 2

void nsLineLayout::SetSpanForEmptyLine(PerSpanData* aPerSpanData,
                                       WritingMode aWM,
                                       const nsSize& aContainerSize,
                                       nscoord aBStartEdge) {
  for (PerFrameData* pfd = aPerSpanData->mFirstFrame; pfd; pfd = pfd->mNext) {
    // Ideally, if the frame would collapse itself - but it depends on
    // knowing that the line is empty.
    if (!pfd->mFrame->IsInlineFrame() && !pfd->mFrame->IsRubyFrame() &&
        !pfd->mFrame->IsPlaceholderFrame()) {
      continue;
    }
    // Collapse the physical size to 0.
    pfd->mBounds.BStart(aWM) = aBStartEdge;
    pfd->mBounds.BSize(aWM) = 0;
    // Initialize mBlockDirAlign (though it doesn't make much difference
    // because we don't align empty boxes).
    pfd->mBlockDirAlign = VALIGN_OTHER;
    pfd->mFrame->SetRect(aWM, pfd->mBounds, aContainerSize);
    if (pfd->mSpan) {
      // For child spans, the block-start edge is relative to that of parent,
      // which is zero (since it is empty). See NOTE in
      // `nsLineLayout::ReflowFrame`.
      SetSpanForEmptyLine(pfd->mSpan, aWM, aContainerSize, 0);
    }
  }
}

void nsLineLayout::VerticalAlignLine() {
  // Partially place the children of the block frame. The baseline for
  // this operation is set to zero so that the y coordinates for all
  // of the placed children will be relative to there.
  PerSpanData* psd = mRootSpan;
  if (mLineIsEmpty) {
    // This line is empty, and should be consisting of only inline elements.
    // (inline-block elements would make the line non-empty).
    SetSpanForEmptyLine(psd, mRootSpan->mWritingMode, ContainerSize(),
                        mBStartEdge);

    mFinalLineBSize = 0;
    if (mGotLineBox) {
      mLineBox->SetBounds(psd->mWritingMode, psd->mIStart, mBStartEdge,
                          psd->mICoord - psd->mIStart, 0, ContainerSize());

      mLineBox->SetLogicalAscent(0);
    }
    return;
  }
  VerticalAlignFrames(psd);

  // *** Note that comments here still use the anachronistic term
  // "line-height" when we really mean "size of the line in the block
  // direction", "vertical-align" when we really mean "alignment in
  // the block direction", and "top" and "bottom" when we really mean
  // "block start" and "block end". This is partly for brevity and
  // partly to retain the association with the CSS line-height and
  // vertical-align properties.
  //
  // Compute the line-height. The line-height will be the larger of:
  //
  // [1] maxBCoord - minBCoord (the distance between the first child's
  // block-start edge and the last child's block-end edge)
  //
  // [2] the maximum logical box block size (since not every frame may have
  // participated in #1; for example: "top" and "botttom" aligned frames)
  //
  // [3] the minimum line height ("line-height" property set on the
  // block frame)
  nscoord lineBSize = psd->mMaxBCoord - psd->mMinBCoord;

  // Now that the line-height is computed, we need to know where the
  // baseline is in the line. Position baseline so that mMinBCoord is just
  // inside the start of the line box.
  nscoord baselineBCoord = mBStartEdge - std::min(0, psd->mMinBCoord);

  // It's also possible that the line block-size isn't tall enough because
  // of "top" and "bottom" aligned elements that were not accounted for in
  // min/max BCoord.
  //
  // The CSS2 spec doesn't really say what happens when to the
  // baseline in this situations. What we do is if the largest start
  // aligned box block size is greater than the line block-size then we leave
  // the baseline alone. If the largest end aligned box is greater
  // than the line block-size then we slide the baseline forward by the extra
  // amount.
  //
  // Navigator 4 gives precedence to the first top/bottom aligned
  // object.  We just let block end aligned objects win.
  if (lineBSize < mMaxEndBoxBSize) {
    // When the line is shorter than the maximum block start aligned box
    nscoord extra = mMaxEndBoxBSize - lineBSize;
    baselineBCoord += extra;
    lineBSize = mMaxEndBoxBSize;
  }
  lineBSize = std::max(lineBSize, mMaxStartBoxBSize);
#ifdef NOISY_BLOCKDIR_ALIGN
  printf(" [line]==> lineBSize=%d baselineBCoord=%d\n", lineBSize,
         baselineBCoord);
#endif

  // Now position all of the frames in the root span. We will also
  // recurse over the child spans and place any frames we find with
  // vertical-align: top or bottom.
  // XXX PERFORMANCE: set a bit per-span to avoid the extra work
  // (propagate it upward too)
  WritingMode lineWM = psd->mWritingMode;
  for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
    if (pfd->mBlockDirAlign == VALIGN_OTHER) {
      pfd->mBounds.BStart(lineWM) += baselineBCoord;
      pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSize());
    }
  }
  PlaceTopBottomFrames(psd, -mBStartEdge, lineBSize);

  mFinalLineBSize = lineBSize;
  if (mGotLineBox) {
    // Fill in returned line-box and max-element-width data
    mLineBox->SetBounds(lineWM, psd->mIStart, mBStartEdge,
                        psd->mICoord - psd->mIStart, lineBSize,
                        ContainerSize());

    mLineBox->SetLogicalAscent(baselineBCoord - mBStartEdge);
#ifdef NOISY_BLOCKDIR_ALIGN
    printf(" [line]==> bounds{x,y,w,h}={%d,%d,%d,%d} lh=%d a=%d\n",
           mLineBox->GetBounds().IStart(lineWM),
           mLineBox->GetBounds().BStart(lineWM),
           mLineBox->GetBounds().ISize(lineWM),
           mLineBox->GetBounds().BSize(lineWM), mFinalLineBSize,
           mLineBox->GetLogicalAscent());
#endif
  }
}

// Place frames with CSS property vertical-align: top or bottom.
void nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
                                        nscoord aDistanceFromStart,
                                        nscoord aLineBSize) {
  for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
    PerSpanData* span = pfd->mSpan;
#ifdef DEBUG
    NS_ASSERTION(0xFF != pfd->mBlockDirAlign, "umr");
#endif
    WritingMode lineWM = mRootSpan->mWritingMode;
    nsSize containerSize = ContainerSizeForSpan(psd);
    switch (pfd->mBlockDirAlign) {
      case VALIGN_TOP:
        if (span) {
          pfd->mBounds.BStart(lineWM) = -aDistanceFromStart - span->mMinBCoord;
        } else {
          pfd->mBounds.BStart(lineWM) =
              -aDistanceFromStart + pfd->mMargin.BStart(lineWM);
        }
        pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerSize);
#ifdef NOISY_BLOCKDIR_ALIGN
        printf(" ");
        pfd->mFrame->ListTag(stdout);
        printf(": y=%d dTop=%d [bp.top=%d topLeading=%d]\n",
               pfd->mBounds.BStart(lineWM), aDistanceFromStart,
               span ? pfd->mBorderPadding.BStart(lineWM) : 0,
               span ? span->mBStartLeading : 0);
#endif
        break;
      case VALIGN_BOTTOM:
        if (span) {
          // Compute bottom leading
          pfd->mBounds.BStart(lineWM) =
              -aDistanceFromStart + aLineBSize - span->mMaxBCoord;
        } else {
          pfd->mBounds.BStart(lineWM) = -aDistanceFromStart + aLineBSize -
                                        pfd->mMargin.BEnd(lineWM) -
                                        pfd->mBounds.BSize(lineWM);
        }
        pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerSize);
#ifdef NOISY_BLOCKDIR_ALIGN
        printf(" ");
        pfd->mFrame->ListTag(stdout);
        printf(": y=%d\n", pfd->mBounds.BStart(lineWM));
#endif
        break;
    }
    if (span) {
      nscoord fromStart = aDistanceFromStart + pfd->mBounds.BStart(lineWM);
      PlaceTopBottomFrames(span, fromStart, aLineBSize);
    }
  }
}

static nscoord GetBSizeOfEmphasisMarks(nsIFrame* aSpanFrame, float aInflation) {
  RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
      aSpanFrame->Style(), aSpanFrame->PresContext(), aInflation);
  return fm->MaxHeight();
}

void nsLineLayout::AdjustLeadings(nsIFrame* spanFrame, PerSpanData* psd,
                                  const nsStyleText* aStyleText,
                                  float aInflation,
                                  bool* aZeroEffectiveSpanBox) {
  MOZ_ASSERT(spanFrame == psd->mFrame->mFrame);
  nscoord requiredStartLeading = 0;
  nscoord requiredEndLeading = 0;
  if (spanFrame->IsRubyFrame()) {
    // We may need to extend leadings here for ruby annotations as
    // required by section Line Spacing in the CSS Ruby spec.
    // See http://dev.w3.org/csswg/css-ruby/#line-height
    auto rubyFrame = static_cast<nsRubyFrame*>(spanFrame);
    RubyBlockLeadings leadings = rubyFrame->GetBlockLeadings();
    requiredStartLeading += leadings.mStart;
    requiredEndLeading += leadings.mEnd;
  }
  if (aStyleText->HasEffectiveTextEmphasis()) {
    nscoord bsize = GetBSizeOfEmphasisMarks(spanFrame, aInflation);
    LogicalSide side = aStyleText->TextEmphasisSide(
        mRootSpan->mWritingMode, spanFrame->StyleFont()->mLanguage);
    if (side == LogicalSide::BStart) {
      requiredStartLeading += bsize;
    } else {
      MOZ_ASSERT(side == LogicalSide::BEnd,
                 "emphasis marks must be in block axis");
      requiredEndLeading += bsize;
    }
  }

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=87 H=93 G=89

¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge