/* -*- 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/. */
// It is the caller's responsibility to operate on logical-coordinate objects // with matched writing modes. Failure to do so will be a runtime bug; the // compiler can't catch it, but in debug mode, we'll throw an assertion. // NOTE that in non-debug builds, a writing mode mismatch error will NOT be // detected, yet the results will be nonsense (and may lead to further layout // failures). Therefore, it is important to test (and fuzz-test) writing-mode // support using debug builds.
// Methods in logical-coordinate classes that take another logical-coordinate // object as a parameter should call CHECK_WRITING_MODE on it to verify that // the writing modes match. // (In some cases, there are internal (private) methods that don't do this; // such methods should only be used by other methods that have already checked // the writing modes.) // The check ignores the StyleWritingMode::VERTICAL_SIDEWAYS and // StyleWritingMode::TEXT_SIDEWAYS bit of writing mode, because // this does not affect the interpretation of logical coordinates.
// Logical axis, edge, side and corner constants for use in various places. enumclass LogicalAxis : uint8_t {
Block, Inline,
}; enumclass LogicalEdge : uint8_t { Start, End };
enumclass LineRelativeDir : uint8_t {
Over = static_cast<uint8_t>(LogicalSide::BStart),
Under = static_cast<uint8_t>(LogicalSide::BEnd),
Left = static_cast<uint8_t>(LogicalSide::IStart),
Right = static_cast<uint8_t>(LogicalSide::IEnd)
};
/** * mozilla::WritingMode is an immutable class representing a * writing mode. * * It efficiently stores the writing mode and can rapidly compute * interesting things about it for use in layout. * * Writing modes are computed from the CSS 'direction', * 'writing-mode', and 'text-orientation' properties. * See CSS3 Writing Modes for more information * http://www.w3.org/TR/css3-writing-modes/
*/ class WritingMode { public: /** * Absolute inline flow direction
*/ enumclass InlineDir : uint8_t {
LTR, // text flows horizontally left to right
RTL, // text flows horizontally right to left
TTB, // text flows vertically top to bottom
BTT, // text flows vertically bottom to top
};
/** * Absolute block flow direction
*/ enumclass BlockDir : uint8_t {
TB, // horizontal lines stack top to bottom
RL, // vertical lines stack right to left
LR, // vertical lines stack left to right
};
/** * Return the absolute inline flow direction as an InlineDir
*/
InlineDir GetInlineDir() const { if (IsVertical()) { return IsInlineReversed() ? InlineDir::BTT : InlineDir::TTB;
} return IsInlineReversed() ? InlineDir::RTL : InlineDir::LTR;
}
/** * Return the absolute block flow direction as a BlockDir
*/
BlockDir GetBlockDir() const { if (IsVertical()) { return mWritingMode & StyleWritingMode::VERTICAL_LR ? BlockDir::LR
: BlockDir::RL;
} return BlockDir::TB;
}
/** * Return true if the inline flow direction is against physical direction * (i.e. right-to-left or bottom-to-top). * This occurs when writing-mode is sideways-lr OR direction is rtl (but not * if both of those are true).
*/ bool IsInlineReversed() const { return !!(mWritingMode & StyleWritingMode::INLINE_REVERSED);
}
/** * Return true if bidi direction is LTR.
*/ bool IsBidiLTR() const { return !IsBidiRTL(); }
/** * Return true if bidi direction is RTL.
*/ bool IsBidiRTL() const { return !!(mWritingMode & StyleWritingMode::RTL); }
/** * True if it is vertical and vertical-lr, or is horizontal and bidi LTR.
*/ bool IsPhysicalLTR() const { return IsVertical() ? IsVerticalLR() : IsBidiLTR();
}
/** * True if it is vertical and vertical-rl, or is horizontal and bidi RTL.
*/ bool IsPhysicalRTL() const { return IsVertical() ? IsVerticalRL() : IsBidiRTL();
}
/** * True if vertical-mode block direction is LR (convenience method).
*/ bool IsVerticalLR() const { return GetBlockDir() == BlockDir::LR; }
/** * True if vertical-mode block direction is RL (convenience method).
*/ bool IsVerticalRL() const { return GetBlockDir() == BlockDir::RL; }
/** * True if vertical writing mode, i.e. when * writing-mode: vertical-lr | vertical-rl.
*/ bool IsVertical() const { return !!(mWritingMode & StyleWritingMode::VERTICAL);
}
/** * True if line-over/line-under are inverted from block-start/block-end. * This is true only when writing-mode is vertical-lr.
*/ bool IsLineInverted() const { return !!(mWritingMode & StyleWritingMode::LINE_INVERTED);
}
/** * Block-axis flow-relative to line-relative factor. * May be used as a multiplication factor for block-axis coordinates * to convert between flow- and line-relative coordinate systems (e.g. * positioning an over- or under-line decoration).
*/ int FlowRelativeToLineRelativeFactor() const { return IsLineInverted() ? -1 : 1;
}
/** * True if vertical sideways writing mode, i.e. when * writing-mode: sideways-lr | sideways-rl.
*/ bool IsVerticalSideways() const { return !!(mWritingMode & StyleWritingMode::VERTICAL_SIDEWAYS);
}
/** * True if this is writing-mode: sideways-rl (convenience method).
*/ bool IsSidewaysRL() const { return IsVerticalRL() && IsVerticalSideways(); }
/** * True if this is writing-mode: sideways-lr (convenience method).
*/ bool IsSidewaysLR() const { return IsVerticalLR() && IsVerticalSideways(); }
/** * True if either text-orientation or writing-mode will force all text to be * rendered sideways in vertical lines, in which case we should prefer an * alphabetic baseline; otherwise, the default is centered. * * Note that some glyph runs may be rendered sideways even if this is false, * due to text-orientation:mixed resolution, but in that case the dominant * baseline remains centered.
*/ bool IsSideways() const { return !!(mWritingMode & (StyleWritingMode::VERTICAL_SIDEWAYS |
StyleWritingMode::TEXT_SIDEWAYS));
}
#ifdef DEBUG // Used by CHECK_WRITING_MODE to compare modes without regard for the // StyleWritingMode::VERTICAL_SIDEWAYS or StyleWritingMode::TEXT_SIDEWAYS // flags.
WritingMode IgnoreSideways() const { return WritingMode(mWritingMode._0 & ~(StyleWritingMode::VERTICAL_SIDEWAYS |
StyleWritingMode::TEXT_SIDEWAYS)
._0);
} #endif
/** * Return true if boxes with this writing mode should use central baselines.
*/ bool IsCentralBaseline() const { return IsVertical() && !IsSideways(); }
/** * Return true if boxes with this writing mode should use alphabetical * baselines.
*/ bool IsAlphabeticalBaseline() const { return !IsCentralBaseline(); }
/** * Convert LogicalAxis to PhysicalAxis given the current writing mode.
*/
mozilla::PhysicalAxis PhysicalAxis(LogicalAxis aAxis) const { constbool isInline = aAxis == LogicalAxis::Inline; return isInline == IsVertical() ? PhysicalAxis::Vertical
: PhysicalAxis::Horizontal;
}
static mozilla::Side PhysicalSideForBlockAxis(uint8_t aWritingModeValue,
LogicalEdge aEdge) { // indexes are StyleWritingModeProperty values, which are the same as these // two-bit values: // bit 0 = the StyleWritingMode::VERTICAL value // bit 1 = the StyleWritingMode::VERTICAL_LR value staticconst mozilla::Side kLogicalBlockSides[][2] = {
{eSideTop, eSideBottom}, // horizontal-tb
{eSideRight, eSideLeft}, // vertical-rl
{eSideBottom, eSideTop}, // (horizontal-bt)
{eSideLeft, eSideRight}, // vertical-lr
};
// Ignore the SidewaysMask bit of the writing-mode value, as this has no // effect on the side mappings.
aWritingModeValue &= ~kWritingModeSidewaysMask;
// What's left of the writing-mode should be in the range 0-3:
NS_ASSERTION(aWritingModeValue < 4, "invalid aWritingModeValue value");
mozilla::Side PhysicalSideForInlineAxis(LogicalEdge aEdge) const { // indexes are four-bit values: // bit 0 = the StyleWritingMode::VERTICAL value // bit 1 = the StyleWritingMode::INLINE_REVERSED value // bit 2 = the StyleWritingMode::VERTICAL_LR value // bit 3 = the StyleWritingMode::LINE_INVERTED value // Not all of these combinations can actually be specified via CSS: there // is no horizontal-bt writing-mode, and no text-orientation value that // produces "inverted" text. (The former 'sideways-left' value, no longer // in the spec, would have produced this in vertical-rl mode.) staticconst mozilla::Side kLogicalInlineSides[][2] = {
{eSideLeft, eSideRight}, // horizontal-tb ltr
{eSideTop, eSideBottom}, // vertical-rl ltr
{eSideRight, eSideLeft}, // horizontal-tb rtl
{eSideBottom, eSideTop}, // vertical-rl rtl
{eSideRight, eSideLeft}, // (horizontal-bt) (inverted) ltr
{eSideTop, eSideBottom}, // sideways-lr rtl
{eSideLeft, eSideRight}, // (horizontal-bt) (inverted) rtl
{eSideBottom, eSideTop}, // sideways-lr ltr
{eSideLeft, eSideRight}, // horizontal-tb (inverted) rtl
{eSideTop, eSideBottom}, // vertical-rl (inverted) rtl
{eSideRight, eSideLeft}, // horizontal-tb (inverted) ltr
{eSideBottom, eSideTop}, // vertical-rl (inverted) ltr
{eSideLeft, eSideRight}, // (horizontal-bt) ltr
{eSideTop, eSideBottom}, // vertical-lr ltr
{eSideRight, eSideLeft}, // (horizontal-bt) rtl
{eSideBottom, eSideTop}, // vertical-lr rtl
};
// Inline axis sides depend on all three of writing-mode, text-orientation // and direction, which are encoded in the StyleWritingMode::VERTICAL, // StyleWritingMode::INLINE_REVERSED, StyleWritingMode::VERTICAL_LR and // StyleWritingMode::LINE_INVERTED bits. Use these four bits to index into // kLogicalInlineSides.
static_assert(StyleWritingMode::VERTICAL._0 == 0x01 &&
StyleWritingMode::INLINE_REVERSED._0 == 0x02 &&
StyleWritingMode::VERTICAL_LR._0 == 0x04 &&
StyleWritingMode::LINE_INVERTED._0 == 0x08, "Unexpected values for StyleWritingMode constants!");
uint8_t index = mWritingMode._0 & 0x0F; return kLogicalInlineSides[index][static_cast<uint8_t>(aEdge)];
}
/** * Returns the physical side corresponding to the specified logical side, * given the current writing mode.
*/
mozilla::Side PhysicalSide(LogicalSide aSide) const { if (IsBlock(aSide)) {
static_assert(StyleWritingMode::VERTICAL._0 == 0x01 &&
StyleWritingMode::VERTICAL_LR._0 == 0x04, "Unexpected values for StyleWritingMode constants!"); const uint8_t wm =
((mWritingMode & StyleWritingMode::VERTICAL_LR)._0 >> 1) |
(mWritingMode & StyleWritingMode::VERTICAL)._0; return PhysicalSideForBlockAxis(wm, GetEdge(aSide));
}
/** * Returns the logical side corresponding to the specified * line-relative direction, given the current writing mode.
*/
LogicalSide LogicalSideForLineRelativeDir(LineRelativeDir aDir) const { auto side = static_cast<LogicalSide>(aDir); if (IsInline(side)) { return IsBidiLTR() ? side : GetOppositeSide(side);
} return !IsLineInverted() ? side : GetOppositeSide(side);
}
/** * Construct a default WritingMode, equivalent to specifying * 'writing-mode: horizontal-tb' and 'direction: ltr' in CSS.
*/
WritingMode() : mWritingMode{0} {}
/** * Construct writing mode based on a ComputedStyle.
*/ explicit WritingMode(const ComputedStyle* aComputedStyle) {
NS_ASSERTION(aComputedStyle, "we need an ComputedStyle here");
mWritingMode = aComputedStyle->WritingMode();
}
/** * This function performs fixup for elements with 'unicode-bidi: plaintext', * where inline directionality is derived from the Unicode bidi categories * of the element's content, and not the CSS 'direction' property. * * The WritingMode constructor will have already incorporated the 'direction' * property into our flag bits, so such elements need to use this method * (after resolving the bidi level of their content) to update the direction * bits as needed. * * If it turns out that our bidi direction already matches what plaintext * resolution determined, there's nothing to do here. If it didn't (i.e. if * the rtl-ness doesn't match), then we correct the direction by flipping the * same bits that get flipped in the constructor's CSS 'direction'-based * chunk.
*/ void SetDirectionFromBidiLevel(mozilla::intl::BidiEmbeddingLevel level) { if (level.IsRTL() == IsBidiLTR()) {
mWritingMode ^= StyleWritingMode::RTL | StyleWritingMode::INLINE_REVERSED;
}
}
/** * Compare two WritingModes for equality.
*/ booloperator==(const WritingMode& aOther) const { return mWritingMode == aOther.mWritingMode;
}
/** * Check whether two modes are orthogonal to each other.
*/ bool IsOrthogonalTo(const WritingMode& aOther) const { return IsVertical() != aOther.IsVertical();
}
/** * Returns true if this WritingMode's aLogicalAxis has the same physical * start side as the parallel axis of WritingMode |aOther|. * * @param aLogicalAxis The axis to compare from this WritingMode. * @param aOther The other WritingMode (from which we'll choose the axis * that's parallel to this WritingMode's aLogicalAxis, for * comparison).
*/ bool ParallelAxisStartsOnSameSide(LogicalAxis aLogicalAxis, const WritingMode& aOther) const {
mozilla::Side myStartSide =
this->PhysicalSide(MakeLogicalSide(aLogicalAxis, LogicalEdge::Start));
// Figure out which of aOther's axes is parallel to |this| WritingMode's // aLogicalAxis, and get its physical start side as well.
LogicalAxis otherWMAxis = aOther.IsOrthogonalTo(*this)
? GetOrthogonalAxis(aLogicalAxis)
: aLogicalAxis;
mozilla::Side otherWMStartSide =
aOther.PhysicalSide(MakeLogicalSide(otherWMAxis, LogicalEdge::Start));
NS_ASSERTION(myStartSide % 2 == otherWMStartSide % 2, "Should end up with sides in the same physical axis"); return myStartSide == otherWMStartSide;
}
friendstruct IPC::ParamTraits<WritingMode>; // IMENotification cannot store this class directly since this has some // constructors. Therefore, it stores mWritingMode and recreate the // instance from it. friendstruct widget::IMENotification;
/** * Unknown writing mode (should never actually be stored or used anywhere).
*/ static constexpr uint8_t kUnknownWritingMode = 0xff;
/** * Return a WritingMode representing an unknown value.
*/ staticinline WritingMode Unknown() { return WritingMode(kUnknownWritingMode);
}
/** * Constructing a WritingMode with an arbitrary value is a private operation. * This is currently only used by the Unknown() and IgnoreSideways() methods, * and a friend struct IMENotification.
*/ explicit WritingMode(uint8_t aValue) : mWritingMode{aValue} {}
/** * Logical-coordinate classes: * * There are three sets of coordinate space: * - physical (top, left, bottom, right) * relative to graphics coord system * - flow-relative (block-start, inline-start, block-end, inline-end) * relative to block/inline flow directions * - line-relative (line-over, line-left, line-under, line-right) * relative to glyph orientation / inline bidi directions * See CSS3 Writing Modes for more information * http://www.w3.org/TR/css3-writing-modes/#abstract-box * * For shorthand, B represents the block-axis * I represents the inline-axis * * The flow-relative geometric classes store coords in flow-relative space. * They use a private ns{Point,Size,Rect,Margin} member to store the actual * coordinate values, but reinterpret them as logical instead of physical. * This allows us to easily perform calculations in logical space (provided * writing modes of the operands match), by simply mapping to nsPoint (etc) * methods. * * Physical-coordinate accessors/setters are responsible to translate these * internal logical values as necessary. * * In DEBUG builds, the logical types store their WritingMode and check * that the same WritingMode is passed whenever callers ask them to do a * writing-mode-dependent operation. Non-DEBUG builds do NOT check this, * to avoid the overhead of storing WritingMode fields. * * Open question: do we need a different set optimized for line-relative * math, for use in nsLineLayout and the like? Or is multiplying values * by FlowRelativeToLineRelativeFactor() enough?
*/
// Construct from a writing mode and individual coordinates (which MUST be // values in that writing mode, NOT physical coordinates!)
LogicalPoint(WritingMode aWritingMode, nscoord aI, nscoord aB)
: #ifdef DEBUG
mWritingMode(aWritingMode), #endif
mPoint(aI, aB) {
}
// Construct from a writing mode and a physical point, within a given // containing rectangle's size (defining the conversion between LTR // and RTL coordinates, and between TTB and BTT coordinates).
LogicalPoint(WritingMode aWritingMode, const nsPoint& aPoint, const nsSize& aContainerSize) #ifdef DEBUG
: mWritingMode(aWritingMode) #endif
{ if (aWritingMode.IsVertical()) {
I() = aWritingMode.IsInlineReversed() ? aContainerSize.height - aPoint.y
: aPoint.y;
B() = aWritingMode.IsVerticalLR() ? aPoint.x
: aContainerSize.width - aPoint.x;
} else {
I() = aWritingMode.IsInlineReversed() ? aContainerSize.width - aPoint.x
: aPoint.x;
B() = aPoint.y;
}
}
LogicalPoint operator+(const LogicalPoint& aOther) const {
CHECK_WRITING_MODE(aOther.GetWritingMode()); // In non-debug builds, LogicalPoint does not store the WritingMode, // so the first parameter here (which will always be WritingMode::Unknown()) // is ignored. return LogicalPoint(GetWritingMode(), mPoint.x + aOther.mPoint.x,
mPoint.y + aOther.mPoint.y);
}
LogicalPoint operator-(const LogicalPoint& aOther) const {
CHECK_WRITING_MODE(aOther.GetWritingMode()); // In non-debug builds, LogicalPoint does not store the WritingMode, // so the first parameter here (which will always be WritingMode::Unknown()) // is ignored. return LogicalPoint(GetWritingMode(), mPoint.x - aOther.mPoint.x,
mPoint.y - aOther.mPoint.y);
}
/** * NOTE that in non-DEBUG builds, GetWritingMode() always returns * WritingMode::Unknown(), as the current mode is not stored in the logical- * geometry classes. Therefore, this method is private; it is used ONLY * by the DEBUG-mode checking macros in this class and its friends; * other code is not allowed to ask a logical point for its writing mode, * as this info will simply not be available in non-DEBUG builds. * * Also, in non-DEBUG builds, CHECK_WRITING_MODE does nothing, and the * WritingMode parameter to logical methods will generally be optimized * away altogether.
*/ #ifdef DEBUG
WritingMode GetWritingMode() const { return mWritingMode; } #else
WritingMode GetWritingMode() const { return WritingMode::Unknown(); } #endif
// We don't allow construction of a LogicalPoint with no writing mode.
LogicalPoint() = delete;
// Accessors that don't take or check a WritingMode value. // These are for internal use only; they are called by methods that have // themselves already checked the WritingMode passed by the caller.
nscoord I() const// inline-axis
{ return mPoint.x;
}
nscoord B() const// block-axis
{ return mPoint.y;
}
// We use an nsPoint to hold the coordinates, but reinterpret its .x and .y // fields as the inline and block directions. Hence, this is not exposed // directly, but only through accessors that will map them according to the // writing mode.
nsPoint mPoint;
};
/** * Return a LogicalSize representing this size in a different writing mode
*/
LogicalSize ConvertTo(WritingMode aToMode, WritingMode aFromMode) const { #ifdef DEBUG // In DEBUG builds make sure to return a LogicalSize with the // expected writing mode
CHECK_WRITING_MODE(aFromMode); return aToMode == aFromMode
? *this
: LogicalSize(aToMode, GetPhysicalSize(aFromMode)); #else // optimization for non-DEBUG builds where LogicalSize doesn't store // the writing mode return (aToMode == aFromMode || !aToMode.IsOrthogonalTo(aFromMode))
? *this
: LogicalSize(aToMode, BSize(), ISize()); #endif
}
/** * Test if a size is (0, 0).
*/ bool IsAllZero() const { return IsAllValues(0); } bool IsAllValues(nscoord aValue) const { return ISize() == aValue && BSize() == aValue;
}
/** * Various binary operators on LogicalSize. These are valid ONLY for operands * that share the same writing mode.
*/ booloperator==(const LogicalSize& aOther) const {
CHECK_WRITING_MODE(aOther.GetWritingMode()); return mSize == aOther.mSize;
}
nscoord Side(LogicalSide aSide, WritingMode aWM) const { switch (aSide) { case LogicalSide::BStart: return BStart(aWM); case LogicalSide::BEnd: return BEnd(aWM); case LogicalSide::IStart: return IStart(aWM); case LogicalSide::IEnd: return IEnd(aWM);
}
MOZ_ASSERT_UNREACHABLE("We should handle all sides!"); return BStart(aWM);
}
nscoord& Side(LogicalSide aSide, WritingMode aWM) { switch (aSide) { case LogicalSide::BStart: return BStart(aWM); case LogicalSide::BEnd: return BEnd(aWM); case LogicalSide::IStart: return IStart(aWM); case LogicalSide::IEnd: return IEnd(aWM);
}
MOZ_ASSERT_UNREACHABLE("We should handle all sides!"); return BStart(aWM);
}
/* * Return margin values for line-relative sides, as defined in * http://www.w3.org/TR/css-writing-modes-3/#line-directions: * * line-left * Nominally the side from which LTR text would start. * line-right * Nominally the side from which RTL text would start. (Opposite of * line-left.)
*/
nscoord LineLeft(WritingMode aWritingMode) const { // We don't need to CHECK_WRITING_MODE here because the IStart or IEnd // accessor that we call will do it. return aWritingMode.IsBidiLTR() ? IStart(aWritingMode) : IEnd(aWritingMode);
}
nscoord LineRight(WritingMode aWritingMode) const { return aWritingMode.IsBidiLTR() ? IEnd(aWritingMode) : IStart(aWritingMode);
}
/** * Return a LogicalSize representing the total size of the inline- * and block-dimension margins.
*/
LogicalSize Size(WritingMode aWritingMode) const {
CHECK_WRITING_MODE(aWritingMode); return LogicalSize(aWritingMode, IStartEnd(), BStartEnd());
}
/** * Return a LogicalPoint representing an offset to the start-sides, i.e. * inline-start and block-start.
*/
LogicalPoint StartOffset(WritingMode aWritingMode) const {
CHECK_WRITING_MODE(aWritingMode); return LogicalPoint(aWritingMode, IStart(), BStart());
}
/** * Accessors for physical margins, using our writing mode to convert from * logical values.
*/
nscoord Top(WritingMode aWritingMode) const {
CHECK_WRITING_MODE(aWritingMode); return aWritingMode.IsVertical()
? (aWritingMode.IsInlineReversed() ? IEnd() : IStart())
: BStart();
}
// We want the same result as nsRect, so assert we get it.
MOZ_ASSERT(result ==
nsRect(mIStart, mBStart, mISize, mBSize)
.IsEqualEdges(nsRect(aOther.mIStart, aOther.mBStart,
aOther.mISize, aOther.mBSize))); return result;
}
/** * Return a LogicalRect representing this rect in a different writing mode
*/
LogicalRect ConvertTo(WritingMode aToMode, WritingMode aFromMode, const nsSize& aContainerSize) const {
CHECK_WRITING_MODE(aFromMode); return aToMode == aFromMode
? *this
: LogicalRect(aToMode,
GetPhysicalRect(aFromMode, aContainerSize),
aContainerSize);
}
/** * Set *this to be the rectangle containing the intersection of aRect1 * and aRect2, return whether the intersection is non-empty.
*/ bool IntersectRect(const LogicalRect& aRect1, const LogicalRect& aRect2) {
CHECK_WRITING_MODE(aRect1.mWritingMode);
CHECK_WRITING_MODE(aRect2.mWritingMode); #ifdef DEBUG // Compute using nsRect and assert the results match
nsRect rectDebug;
rectDebug.IntersectRect(
nsRect(aRect1.mIStart, aRect1.mBStart, aRect1.mISize, aRect1.mBSize),
nsRect(aRect2.mIStart, aRect2.mBStart, aRect2.mISize, aRect2.mBSize)); #endif
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.