float TextAutosizer::inflate(LayoutObject* parent, InflateBehavior behavior, float multiplier) { Cluster* cluster = currentCluster(); bool hasTextChild = false; LayoutObject* child = nullptr; if (parent->isLayoutBlock() && (parent->childrenInline() || behavior == DescendToInnerBlocks)) child = toLayoutBlock(parent)->firstChild(); else if (parent->isLayoutInline()) child = toLayoutInline(parent)->firstChild(); while (child) { if (child->isText()) { hasTextChild = true; // We only calculate this multiplier on-demand to ensure the parent block of this text // has entered layout. if (!multiplier) multiplier = cluster->m_flags & SUPPRESSING ? 1.0f : clusterMultiplier(cluster); applyMultiplier(child, multiplier); // FIXME: Investigate why MarkOnlyThis is sufficient. if (parent->isLayoutInline()) child->setPreferredLogicalWidthsDirty(MarkOnlyThis); } else if (child->isLayoutInline()) { multiplier = inflate(child, behavior, multiplier); } else if (child->isLayoutBlock() && behavior == DescendToInnerBlocks && !classifyBlock(child, INDEPENDENT | EXPLICIT_WIDTH | SUPPRESSING)) { multiplier = inflate(child, behavior, multiplier); } child = child->nextSibling(); } if (hasTextChild) { applyMultiplier(parent, multiplier); // Parent handles line spacing. } else if (!parent->isListItem()) { // For consistency, a block with no immediate text child should always have a // multiplier of 1. applyMultiplier(parent, 1); } if (parent->isListItem()) { float multiplier = clusterMultiplier(cluster); applyMultiplier(parent, multiplier); // The list item has to be treated special because we can have a tree such that you have // a list item for a form inside it. The list marker then ends up inside the form and when // we try to get the clusterMultiplier we have the wrong cluster root to work from and get // the wrong value. LayoutListItem* item = toLayoutListItem(parent); if (LayoutListMarker* marker = item->marker()) { applyMultiplier(marker, multiplier); marker->setPreferredLogicalWidthsDirty(MarkOnlyThis); } } return multiplier; }
void TextAutosizer::record(const LayoutBlock* block) { if (!m_pageInfo.m_settingEnabled) return; ASSERT(!m_blocksThatHaveBegunLayout.contains(block)); if (!classifyBlock(block, INDEPENDENT | EXPLICIT_WIDTH)) return; if (Fingerprint fingerprint = computeFingerprint(block)) m_fingerprintMapper.addTentativeClusterRoot(block, fingerprint); }
static CSSValueID classifyVariableRange(CSSParserTokenRange range, bool& hasReferences) { hasReferences = false; range.consumeWhitespace(); if (range.peek().type() == IdentToken) { CSSValueID id = range.consumeIncludingWhitespace().id(); if (range.atEnd() && (id == CSSValueInherit || id == CSSValueInitial || id == CSSValueUnset)) return id; } if (classifyBlock(range, hasReferences)) return CSSValueInternalVariableValue; return CSSValueInvalid; }
bool TextAutosizer::clusterHasEnoughTextToAutosize(Cluster* cluster, const LayoutBlock* widthProvider) { if (cluster->m_hasEnoughTextToAutosize != UnknownAmountOfText) return cluster->m_hasEnoughTextToAutosize == HasEnoughText; const LayoutBlock* root = cluster->m_root; if (!widthProvider) widthProvider = clusterWidthProvider(root); // TextAreas and user-modifiable areas get a free pass to autosize regardless of text content. if (root->isTextArea() || (root->style() && root->style()->userModify() != READ_ONLY)) { cluster->m_hasEnoughTextToAutosize = HasEnoughText; return true; } if (cluster->m_flags & SUPPRESSING) { cluster->m_hasEnoughTextToAutosize = NotEnoughText; return false; } // 4 lines of text is considered enough to autosize. float minimumTextLengthToAutosize = widthFromBlock(widthProvider) * 4; float length = 0; LayoutObject* descendant = root->firstChild(); while (descendant) { if (descendant->isLayoutBlock()) { if (classifyBlock(descendant, INDEPENDENT | SUPPRESSING)) { descendant = descendant->nextInPreOrderAfterChildren(root); continue; } } else if (descendant->isText()) { // Note: Using text().stripWhiteSpace().length() instead of resolvedTextLength() because // the lineboxes will not be built until layout. These values can be different. // Note: This is an approximation assuming each character is 1em wide. length += toLayoutText(descendant)->text().stripWhiteSpace().length() * descendant->style()->specifiedFontSize(); if (length >= minimumTextLengthToAutosize) { cluster->m_hasEnoughTextToAutosize = HasEnoughText; return true; } } descendant = descendant->nextInPreOrder(root); } cluster->m_hasEnoughTextToAutosize = NotEnoughText; return false; }
bool isValidVariableReference(CSSParserTokenRange range) { range.consumeWhitespace(); if (!CSSVariableParser::isValidVariableName(range.consumeIncludingWhitespace())) return false; if (range.atEnd()) return true; if (range.consume().type() != CommaToken) return false; if (range.atEnd()) return false; bool hasReferences = false; return classifyBlock(range, hasReferences); }
bool classifyBlock(CSSParserTokenRange range, bool& hasReferences, bool isTopLevelBlock = true) { while (!range.atEnd()) { if (range.peek().blockType() == CSSParserToken::BlockStart) { const CSSParserToken& token = range.peek(); CSSParserTokenRange block = range.consumeBlock(); if (token.functionId() == CSSValueVar) { if (!isValidVariableReference(block)) return false; // Bail if any references are invalid hasReferences = true; continue; } if (!classifyBlock(block, hasReferences, false)) return false; continue; } ASSERT(range.peek().blockType() != CSSParserToken::BlockEnd); const CSSParserToken& token = range.consume(); switch (token.type()) { case DelimiterToken: { if (token.delimiter() == '!' && isTopLevelBlock) return false; break; } case RightParenthesisToken: case RightBraceToken: case RightBracketToken: case BadStringToken: case BadUrlToken: return false; case SemicolonToken: if (isTopLevelBlock) return false; break; default: break; } } return true; }
TextAutosizer::Cluster* TextAutosizer::maybeCreateCluster(const LayoutBlock* block) { BlockFlags flags = classifyBlock(block); if (!(flags & POTENTIAL_ROOT)) return nullptr; Cluster* parentCluster = m_clusterStack.isEmpty() ? nullptr : currentCluster(); ASSERT(parentCluster || block->isLayoutView()); // If a non-independent block would not alter the SUPPRESSING flag, it doesn't need to be a cluster. bool parentSuppresses = parentCluster && (parentCluster->m_flags & SUPPRESSING); if (!(flags & INDEPENDENT) && !(flags & EXPLICIT_WIDTH) && !!(flags & SUPPRESSING) == parentSuppresses) return nullptr; Cluster* cluster = new Cluster(block, flags, parentCluster, getSupercluster(block)); #ifdef AUTOSIZING_DOM_DEBUG_INFO // Non-SUPPRESSING clusters are annotated in clusterMultiplier. if (flags & SUPPRESSING) writeClusterDebugInfo(cluster); #endif return cluster; }
const LayoutObject* TextAutosizer::findTextLeaf(const LayoutObject* parent, size_t& depth, TextLeafSearch firstOrLast) const { // List items are treated as text due to the marker. if (parent->isListItem()) return parent; if (parent->isText()) return parent; ++depth; const LayoutObject* child = (firstOrLast == First) ? parent->slowFirstChild() : parent->slowLastChild(); while (child) { // Note: At this point clusters may not have been created for these blocks so we cannot rely // on m_clusters. Instead, we use a best-guess about whether the block will become a cluster. if (!classifyBlock(child, INDEPENDENT)) { if (const LayoutObject* leaf = findTextLeaf(child, depth, firstOrLast)) return leaf; } child = (firstOrLast == First) ? child->nextSibling() : child->previousSibling(); } --depth; return nullptr; }
bool TextAutosizer::clusterWouldHaveEnoughTextToAutosize(const LayoutBlock* root, const LayoutBlock* widthProvider) { Cluster hypotheticalCluster(root, classifyBlock(root), nullptr); return clusterHasEnoughTextToAutosize(&hypotheticalCluster, widthProvider); }
// // Perform classification // // Input: trainSet, testSet // Output: vector of assigned labels // void CacheEfficient_kNCN::classify(const SampleSet& trainSet, const SampleSet& testSet) { for (int testBlockIndex = 0; testBlockIndex < nrTestSetBlocks - 1; testBlockIndex++) { SampleSet testSetBlock(testSet.nrClasses, testSet.nrDims, nrSamplesInTestBlock); for (int samIndex = 0; samIndex < nrSamplesInTestBlock; samIndex++) { testSetBlock[samIndex] = testSet[testBlockIndex*nrSamplesInTestBlock + samIndex]; } for (int trainBlockIndex = 0; trainBlockIndex < nrTrainSetBlocks - 1; trainBlockIndex++) { SampleSet trainSetBlock(trainSet.nrClasses, trainSet.nrDims, nrSamplesInTrainBlock); for (int samIndex = 0; samIndex < nrSamplesInTrainBlock; samIndex++) { trainSetBlock[samIndex] = trainSet[trainBlockIndex*nrSamplesInTrainBlock + samIndex]; } classifyBlock(trainSetBlock, testSetBlock, this->blockDistances[testBlockIndex][trainBlockIndex], this->blockNCNDists[testBlockIndex][trainBlockIndex], trainBlockIndex*nrSamplesInTrainBlock, testBlockIndex*nrSamplesInTestBlock); } // last train block SampleSet trainSetBlock(trainSet.nrClasses, trainSet.nrDims, nrRemainingTrainSamples); for (int samIndex = 0; samIndex < trainSetBlock.nrSamples; samIndex++) { trainSetBlock[samIndex] = trainSet[firstRemainingTrainSampleIndex + samIndex]; } classifyBlock(trainSetBlock, testSetBlock, this->blockDistances[testBlockIndex][nrTrainSetBlocks - 1], this->blockNCNDists[testBlockIndex][nrTrainSetBlocks - 1], firstRemainingTrainSampleIndex, testBlockIndex*nrSamplesInTestBlock); // store NCNs for every testSample from current testSetBlock from all trainBlocks for every k for (int samIndex = 0; samIndex < testSetBlock.nrSamples; samIndex++) { for (int trainBlockIndex = 0; trainBlockIndex < nrTrainSetBlocks; trainBlockIndex++) { for (int kIndex = 0; kIndex < k; kIndex++) { closestNCNDists[samIndex][trainBlockIndex*k + kIndex] = blockNCNDists[testBlockIndex][trainBlockIndex][samIndex][kIndex]; } } } for (int samIndex = 0; samIndex < testSetBlock.nrSamples; samIndex++) { // sort NCNs on basis of distance from sample qsort(closestNCNDists[samIndex], nrTrainSetBlocks*k, sizeof(NCNDistance), compareNCNDistances); // assign label on basis of closestNCNDists results[testBlockIndex*nrSamplesInTestBlock + samIndex] = assignLabel(closestNCNDists[samIndex], k); //LOG4CXX_DEBUG(logger, "" << closestNCNDists[samIndex][0].sampleIndex << " " << closestNCNDists[samIndex][0].distValue // << " " << closestNCNDists[samIndex][1].sampleIndex << " " << closestNCNDists[samIndex][1].distValue // << " " << closestNCNDists[samIndex][2].sampleIndex << " " << closestNCNDists[samIndex][2].distValue // << " " << closestNCNDists[samIndex][3].sampleIndex << " " << closestNCNDists[samIndex][3].distValue // << " " << closestNCNDists[samIndex][4].sampleIndex << " " << closestNCNDists[samIndex][4].distValue // ); } } // last test block SampleSet testSetBlock(testSet.nrClasses, testSet.nrDims, nrRemainingTestSamples); for (int samIndex = 0; samIndex < nrRemainingTestSamples; samIndex++) { testSetBlock[samIndex] = testSet[firstRemainingTestSampleIndex + samIndex]; } for (int trainBlockIndex = 0; trainBlockIndex < nrTrainSetBlocks - 1; trainBlockIndex++) { SampleSet trainSetBlock(trainSet.nrClasses, trainSet.nrDims, nrSamplesInTrainBlock); for (int samIndex = 0; samIndex < nrSamplesInTrainBlock; samIndex++) { trainSetBlock[samIndex] = trainSet[trainBlockIndex*nrSamplesInTrainBlock + samIndex]; } classifyBlock(trainSetBlock, testSetBlock, this->blockDistances[nrTestSetBlocks - 1][trainBlockIndex], this->blockNCNDists[nrTestSetBlocks - 1][trainBlockIndex], trainBlockIndex*nrSamplesInTrainBlock, firstRemainingTestSampleIndex); } // last train block for last test block SampleSet trainSetBlock(trainSet.nrClasses, trainSet.nrDims, nrRemainingTrainSamples); for (int samIndex = 0; samIndex < nrRemainingTrainSamples; samIndex++) { trainSetBlock[samIndex] = trainSet[firstRemainingTrainSampleIndex + samIndex]; } classifyBlock(trainSetBlock, testSetBlock, this->blockDistances[nrTestSetBlocks - 1][nrTrainSetBlocks - 1], this->blockNCNDists[nrTestSetBlocks - 1][nrTrainSetBlocks - 1], firstRemainingTrainSampleIndex, firstRemainingTestSampleIndex); // find the closest candidate for every testSample from current testSetBlock for kNCN from all trainBlocks for every k for (int samIndex = 0; samIndex < testSetBlock.nrSamples; samIndex++) { for (int trainBlockIndex = 0; trainBlockIndex < nrTrainSetBlocks; trainBlockIndex++) { for (int kIndex = 0; kIndex < k; kIndex++) { closestNCNDists[samIndex][trainBlockIndex*k + kIndex] = blockNCNDists[nrTestSetBlocks - 1][trainBlockIndex][samIndex][kIndex]; } } } for (int samIndex = 0; samIndex < testSetBlock.nrSamples; samIndex++) { // sort NCNs on basis of distance from sample qsort(closestNCNDists[samIndex], nrTrainSetBlocks*k, sizeof(NCNDistance), compareNCNDistances); // assign label on basis of closestNCNDists results[firstRemainingTestSampleIndex + samIndex] = assignLabel(closestNCNDists[samIndex], k); //LOG4CXX_DEBUG(logger, "" << closestNCNDists[samIndex][0].sampleIndex << " " << closestNCNDists[samIndex][0].distValue // << " " << closestNCNDists[samIndex][1].sampleIndex << " " << closestNCNDists[samIndex][1].distValue // << " " << closestNCNDists[samIndex][2].sampleIndex << " " << closestNCNDists[samIndex][2].distValue // << " " << closestNCNDists[samIndex][3].sampleIndex << " " << closestNCNDists[samIndex][3].distValue // << " " << closestNCNDists[samIndex][4].sampleIndex << " " << closestNCNDists[samIndex][4].distValue // ); } }