TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style, const SVGTextFragment& fragment) const
{
    ASSERT(style);

    TextRun run(StringView(renderer().text()).substring(fragment.characterOffset, fragment.length)
                , 0 /* xPos, only relevant with allowTabs=true */
                , 0 /* padding, only relevant for justified text, not relevant for SVG */
                , AllowTrailingExpansion
                , direction()
                , dirOverride() || style->rtlOrdering() == VisualOrder /* directionalOverride */);

    // We handle letter & word spacing ourselves.
    run.disableSpacing();

    // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring.
    run.setCharactersLength(renderer().textLength() - fragment.characterOffset);
    ASSERT(run.charactersLength() >= run.length());
    return run;
}
Пример #2
0
inline SearchBuffer::SearchBuffer(const String& target, FindOptions options)
    : m_options(options),
      m_prefixLength(0),
      m_numberOfCharactersJustAppended(0),
      m_atBreak(true),
      m_needsMoreContext(options & AtWordStarts),
      m_targetRequiresKanaWorkaround(containsKanaLetters(target)) {
  DCHECK(!target.isEmpty()) << target;
  target.appendTo(m_target);

  // FIXME: We'd like to tailor the searcher to fold quote marks for us instead
  // of doing it in a separate replacement pass here, but ICU doesn't offer a
  // way to add tailoring on top of the locale-specific tailoring as of this
  // writing.
  foldQuoteMarksAndSoftHyphens(m_target.data(), m_target.size());

  size_t targetLength = m_target.size();
  m_buffer.reserveInitialCapacity(
      std::max(targetLength * 8, kMinimumSearchBufferSize));
  m_overlap = m_buffer.capacity() / 4;

  if ((m_options & AtWordStarts) && targetLength) {
    const UChar32 targetFirstCharacter =
        getCodePointAt(m_target.data(), 0, targetLength);
    // Characters in the separator category never really occur at the beginning
    // of a word, so if the target begins with such a character, we just ignore
    // the AtWordStart option.
    if (isSeparator(targetFirstCharacter)) {
      m_options &= ~AtWordStarts;
      m_needsMoreContext = false;
    }
  }

  m_textSearcher = WTF::makeUnique<TextSearcherICU>();
  m_textSearcher->setPattern(StringView(m_target.data(), m_target.size()),
                             !(m_options & CaseInsensitive));

  // The kana workaround requires a normalized copy of the target string.
  if (m_targetRequiresKanaWorkaround)
    normalizeCharactersIntoNFCForm(m_target.data(), m_target.size(),
                                   m_normalizedTarget);
}
Пример #3
0
Vector<TextCheckingResult> TextChecker::checkTextOfParagraph(int64_t spellDocumentTag, const UChar* text, int length, uint64_t checkingTypes)
{
    Vector<TextCheckingResult> paragraphCheckingResult;
#if ENABLE(SPELLCHECK)
    if (checkingTypes & TextCheckingTypeSpelling) {
        TextBreakIterator* textIterator = wordBreakIterator(StringView(text, length));
        if (!textIterator)
            return paragraphCheckingResult;

        // Omit the word separators at the beginning/end of the text to don't unnecessarily
        // involve the client to check spelling for them.
        int offset = nextWordOffset(text, length, 0);
        int lengthStrip = length;
        while (lengthStrip > 0 && isTextBreak(textIterator, lengthStrip - 1))
            --lengthStrip;

        while (offset >= 0 && offset < lengthStrip) {
            int32_t misspellingLocation = -1;
            int32_t misspellingLength = 0;
            checkSpellingOfString(spellDocumentTag, text + offset, lengthStrip - offset, misspellingLocation, misspellingLength);
            if (!misspellingLength)
                break;

            TextCheckingResult misspellingResult;
            misspellingResult.type = TextCheckingTypeSpelling;
            misspellingResult.location = offset + misspellingLocation;
            misspellingResult.length = misspellingLength;
            paragraphCheckingResult.append(misspellingResult);
            offset += misspellingLocation + misspellingLength;
            // Generally, we end up checking at the word separator, move to the adjacent word.
            offset = nextWordOffset(text, lengthStrip, offset);
        }
    }
#else
    UNUSED_PARAM(spellDocumentTag);
    UNUSED_PARAM(text);
    UNUSED_PARAM(length);
    UNUSED_PARAM(checkingTypes);
#endif
    return paragraphCheckingResult;
}
Пример #4
0
ExceptionOr<void> SVGLengthValue::setValueAsString(const String& string)
{
    if (string.isEmpty())
        return { };

    float convertedNumber = 0;
    auto upconvertedCharacters = StringView(string).upconvertedCharacters();
    const UChar* ptr = upconvertedCharacters;
    const UChar* end = ptr + string.length();

    if (!parseNumber(ptr, end, convertedNumber, false))
        return Exception { SYNTAX_ERR };

    auto type = parseLengthType(ptr, end);
    if (type == LengthTypeUnknown)
        return Exception { SYNTAX_ERR };

    m_unit = storeUnit(extractMode(m_unit), type);
    m_valueInSpecifiedUnits = convertedNumber;
    return { };
}
Пример #5
0
ExceptionOr<void> SVGAngleValue::setValueAsString(const String& value)
{
    if (value.isEmpty()) {
        m_unitType = SVG_ANGLETYPE_UNSPECIFIED;
        return { };
    }

    auto upconvertedCharacters = StringView(value).upconvertedCharacters();
    const UChar* ptr = upconvertedCharacters;
    const UChar* end = ptr + value.length();

    float valueInSpecifiedUnits = 0;
    if (!parseNumber(ptr, end, valueInSpecifiedUnits, false))
        return Exception { SYNTAX_ERR };

    auto unitType = parseAngleType(ptr, end);
    if (unitType == SVG_ANGLETYPE_UNKNOWN)
        return Exception { SYNTAX_ERR };

    m_unitType = unitType;
    m_valueInSpecifiedUnits = valueInSpecifiedUnits;
    return { };
}
Пример #6
0
void SVGLengthListValues::parse(const String& value, SVGLengthMode mode)
{
    clear();

    auto upconvertedCharacters = StringView(value).upconvertedCharacters();
    const UChar* ptr = upconvertedCharacters;
    const UChar* end = ptr + value.length();
    while (ptr < end) {
        const UChar* start = ptr;
        while (ptr < end && *ptr != ',' && !isSVGSpace(*ptr))
            ptr++;
        if (ptr == start)
            break;

        SVGLengthValue length(mode);
        String valueString(start, ptr - start);
        if (valueString.isEmpty())
            return;
        if (length.setValueAsString(valueString).hasException())
            return;
        append(length);
        skipOptionalSVGSpacesOrDelimiter(ptr, end);
    }
}
Пример #7
0
static bool parsePoint(const String& s, FloatPoint& point)
{
    if (s.isEmpty())
        return false;
    auto upconvertedCharacters = StringView(s).upconvertedCharacters();
    const UChar* cur = upconvertedCharacters;
    const UChar* end = cur + s.length();
    
    if (!skipOptionalSVGSpaces(cur, end))
        return false;
    
    float x = 0;
    if (!parseNumber(cur, end, x))
        return false;
    
    float y = 0;
    if (!parseNumber(cur, end, y))
        return false;
    
    point = FloatPoint(x, y);
    
    // disallow anything except spaces at the end
    return !skipOptionalSVGSpaces(cur, end);
}
Пример #8
0
	bool UrlView::parse(const StringView& _url)
	{
		clear();

		const char* start = _url.getPtr();
		const char* term  = _url.getTerm();
		const char* schemeEnd = strFind(StringView(start, term), "://");
		const char* hostStart = NULL != schemeEnd ? schemeEnd+3 : start;
		const char* pathStart = strFind(StringView(hostStart, term), '/');

		if (NULL == schemeEnd
		&&  NULL == pathStart)
		{
			return false;
		}

		if (NULL != schemeEnd
		&& (NULL == pathStart || pathStart > schemeEnd) )
		{
			StringView scheme(start, schemeEnd);

			if (!isAlpha(scheme) )
			{
				return false;
			}

			m_tokens[Scheme].set(scheme);
		}

		if (NULL != pathStart)
		{
			const char* queryStart    = strFind(StringView(pathStart, term), '?');
			const char* fragmentStart = strFind(StringView(pathStart, term), '#');

			if (NULL != fragmentStart
			&&  fragmentStart < queryStart)
			{
				return false;
			}

			m_tokens[Path].set(pathStart
				, NULL != queryStart    ? queryStart
				: NULL != fragmentStart ? fragmentStart
				: term
				);

			if (NULL != queryStart)
			{
				m_tokens[Query].set(queryStart+1
					, NULL != fragmentStart ? fragmentStart
					: term
					);
			}

			if (NULL != fragmentStart)
			{
				m_tokens[Fragment].set(fragmentStart+1, term);
			}

			term = pathStart;
		}

		const char* userPassEnd   = strFind(StringView(hostStart, term), '@');
		const char* userPassStart = NULL != userPassEnd ? hostStart : NULL;
		hostStart = NULL != userPassEnd ? userPassEnd+1 : hostStart;
		const char* portStart = strFind(StringView(hostStart, term), ':');

		m_tokens[Host].set(hostStart, NULL != portStart ? portStart : term);

		if (NULL != portStart)
		{
			m_tokens[Port].set(portStart+1, term);
		}

		if (NULL != userPassStart)
		{
			const char* passStart = strFind(StringView(userPassStart, userPassEnd), ':');

			m_tokens[UserName].set(userPassStart
				, NULL != passStart ? passStart
				: userPassEnd
				);

			if (NULL != passStart)
			{
				m_tokens[Password].set(passStart+1, userPassEnd);
			}
		}

		return true;
	}
void SVGPreserveAspectRatio::parse(const String& value)
{
    auto upconvertedCharacters = StringView(value).upconvertedCharacters();
    const UChar* begin = upconvertedCharacters;
    parseInternal(begin, begin + value.length(), true);
}
Пример #10
0
StringView StringView::slice(Size startIndex, Size sliceSize) const {
  assert(data != nullptr || size == 0);
  assert(startIndex <= size && startIndex + sliceSize <= size);
  return StringView(data + startIndex, sliceSize);
}
Пример #11
0
void DesktopShellSettings::setKeymap(const char *layout)
{
    for (Seat *seat: m_shell->compositor()->seats()) {
        seat->setKeymap(Keymap(StringView(layout), Maybe<StringView>(), Maybe<StringView>()));
    }
}
Пример #12
0
bool parseManifest(const URL& manifestURL, const char* data, int length, Manifest& manifest)
{
    ASSERT(manifest.explicitURLs.isEmpty());
    ASSERT(manifest.onlineWhitelistedURLs.isEmpty());
    ASSERT(manifest.fallbackURLs.isEmpty());
    manifest.allowAllNetworkRequests = false;

    Mode mode = Explicit;

    String s = TextResourceDecoder::create("text/cache-manifest", "UTF-8")->decodeAndFlush(data, length);
    
    // Look for the magic signature: "^\xFEFF?CACHE MANIFEST[ \t]?" (the BOM is removed by TextResourceDecoder).
    // Example: "CACHE MANIFEST #comment" is a valid signature.
    // Example: "CACHE MANIFEST;V2" is not.
    if (!s.startsWith("CACHE MANIFEST"))
        return false;
    
    StringView manifestAfterSignature = StringView(s).substring(14); // "CACHE MANIFEST" is 14 characters.
    auto upconvertedCharacters = manifestAfterSignature.upconvertedCharacters();
    const UChar* p = upconvertedCharacters;
    const UChar* end = p + manifestAfterSignature.length();

    if (p < end && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')
        return false;

    // Skip to the end of the line.
    while (p < end && *p != '\r' && *p != '\n')
        p++;

    while (1) {
        // Skip whitespace
        while (p < end && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t'))
            p++;
        
        if (p == end)
            break;
        
        const UChar* lineStart = p;
        
        // Find the end of the line
        while (p < end && *p != '\r' && *p != '\n')
            p++;
        
        // Check if we have a comment
        if (*lineStart == '#')
            continue;
        
        // Get rid of trailing whitespace
        const UChar* tmp = p - 1;
        while (tmp > lineStart && (*tmp == ' ' || *tmp == '\t'))
            tmp--;
        
        String line(lineStart, tmp - lineStart + 1);

        if (line == "CACHE:") 
            mode = Explicit;
        else if (line == "FALLBACK:")
            mode = Fallback;
        else if (line == "NETWORK:")
            mode = OnlineWhitelist;
        else if (line.endsWith(':'))
            mode = Unknown;
        else if (mode == Unknown)
            continue;
        else if (mode == Explicit || mode == OnlineWhitelist) {
            auto upconvertedLineCharacters = StringView(line).upconvertedCharacters();
            const UChar* p = upconvertedLineCharacters;
            const UChar* lineEnd = p + line.length();
            
            // Look for whitespace separating the URL from subsequent ignored tokens.
            while (p < lineEnd && *p != '\t' && *p != ' ') 
                p++;

            if (mode == OnlineWhitelist && p - upconvertedLineCharacters == 1 && line[0] == '*') {
                // Wildcard was found.
                manifest.allowAllNetworkRequests = true;
                continue;
            }

            URL url(manifestURL, line.substring(0, p - upconvertedLineCharacters));
            
            if (!url.isValid())
                continue;

            if (url.hasFragmentIdentifier())
                url.removeFragmentIdentifier();
            
            if (!equalIgnoringASCIICase(url.protocol(), manifestURL.protocol()))
                continue;
            
            if (mode == Explicit && manifestURL.protocolIs("https") && !protocolHostAndPortAreEqual(manifestURL, url))
                continue;
            
            if (mode == Explicit)
                manifest.explicitURLs.add(url.string());
            else
                manifest.onlineWhitelistedURLs.append(url);
            
        } else if (mode == Fallback) {
            auto upconvertedLineCharacters = StringView(line).upconvertedCharacters();
            const UChar* p = upconvertedLineCharacters;
            const UChar* lineEnd = p + line.length();
            
            // Look for whitespace separating the two URLs
            while (p < lineEnd && *p != '\t' && *p != ' ') 
                p++;

            if (p == lineEnd) {
                // There was no whitespace separating the URLs.
                continue;
            }
            
            URL namespaceURL(manifestURL, line.substring(0, p - upconvertedLineCharacters));
            if (!namespaceURL.isValid())
                continue;
            if (namespaceURL.hasFragmentIdentifier())
                namespaceURL.removeFragmentIdentifier();

            if (!protocolHostAndPortAreEqual(manifestURL, namespaceURL))
                continue;
                                   
            // Skip whitespace separating fallback namespace from URL.
            while (p < lineEnd && (*p == '\t' || *p == ' '))
                p++;

            // Look for whitespace separating the URL from subsequent ignored tokens.
            const UChar* fallbackStart = p;
            while (p < lineEnd && *p != '\t' && *p != ' ') 
                p++;

            URL fallbackURL(manifestURL, String(fallbackStart, p - fallbackStart));
            if (!fallbackURL.isValid())
                continue;
            if (fallbackURL.hasFragmentIdentifier())
                fallbackURL.removeFragmentIdentifier();

            if (!protocolHostAndPortAreEqual(manifestURL, fallbackURL))
                continue;

            manifest.fallbackURLs.append(std::make_pair(namespaceURL, fallbackURL));            
        } else 
            ASSERT_NOT_REACHED();
    }

    return true;
}
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style)
{
    if (m_inPathLayout && m_textPath.isEmpty())
        return;

    RenderElement* textParent = text->parent();
    ASSERT(textParent);
    SVGElement* lengthContext = downcast<SVGElement>(textParent->element());
    
    bool definesTextLength = parentDefinesTextLength(textParent);

    const SVGRenderStyle& svgStyle = style->svgStyle();

    m_visualMetricsListOffset = 0;
    m_visualCharacterOffset = 0;

    Vector<SVGTextMetrics>& visualMetricsValues = text->layoutAttributes()->textMetricsValues();
    ASSERT(!visualMetricsValues.isEmpty());

    auto upconvertedCharacters = StringView(text->text()).upconvertedCharacters();
    const UChar* characters = upconvertedCharacters;
    const Font& font = style->font();

    SVGTextLayoutEngineSpacing spacingLayout(font);
    SVGTextLayoutEngineBaseline baselineLayout(font);

    bool didStartTextFragment = false;
    bool applySpacingToNextCharacter = false;

    float lastAngle = 0;
    float baselineShift = baselineLayout.calculateBaselineShift(&svgStyle, lengthContext);
    baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text);

    // Main layout algorithm.
    while (true) {
        // Find the start of the current text box in this list, respecting ligatures.
        SVGTextMetrics visualMetrics(SVGTextMetrics::SkippedSpaceMetrics);
        if (!currentVisualCharacterMetrics(textBox, visualMetricsValues, visualMetrics))
            break;

        if (visualMetrics.isEmpty()) {
            advanceToNextVisualCharacter(visualMetrics);
            continue;
        }

        SVGTextLayoutAttributes* logicalAttributes = 0;
        if (!currentLogicalCharacterAttributes(logicalAttributes))
            break;

        ASSERT(logicalAttributes);
        SVGTextMetrics logicalMetrics(SVGTextMetrics::SkippedSpaceMetrics);
        if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics))
            break;

        SVGCharacterDataMap& characterDataMap = logicalAttributes->characterDataMap();
        SVGCharacterData data;
        SVGCharacterDataMap::iterator it = characterDataMap.find(m_logicalCharacterOffset + 1);
        if (it != characterDataMap.end())
            data = it->value;

        float x = data.x;
        float y = data.y;

        // When we've advanced to the box start offset, determine using the original x/y values,
        // whether this character starts a new text chunk, before doing any further processing.
        if (m_visualCharacterOffset == textBox->start())
            textBox->setStartsNewTextChunk(logicalAttributes->context().characterStartsNewTextChunk(m_logicalCharacterOffset));

        float angle = data.rotate == SVGTextLayoutAttributes::emptyValue() ? 0 : data.rotate;

        // Calculate glyph orientation angle.
        const UChar* currentCharacter = characters + m_visualCharacterOffset;
        float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, &svgStyle, *currentCharacter);

        // Calculate glyph advance & x/y orientation shifts.
        float xOrientationShift = 0;
        float yOrientationShift = 0;
        float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, visualMetrics, orientationAngle, xOrientationShift, yOrientationShift);

        // Assign current text position to x/y values, if needed.
        updateCharacerPositionIfNeeded(x, y);

        // Apply dx/dy value adjustments to current text position, if needed.
        updateRelativePositionAdjustmentsIfNeeded(data.dx, data.dy);

        // Calculate SVG Fonts kerning, if needed.
        float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, visualMetrics.glyph());

        // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed.
        float spacing = spacingLayout.calculateCSSKerningAndSpacing(&svgStyle, lengthContext, currentCharacter);

        float textPathOffset = 0;
        if (m_inPathLayout) {
            float scaledGlyphAdvance = glyphAdvance * m_textPathScaling;
            if (m_isVerticalText) {
                // If there's an absolute y position available, it marks the beginning of a new position along the path.
                if (y != SVGTextLayoutAttributes::emptyValue())
                    m_textPathCurrentOffset = y + m_textPathStartOffset;

                m_textPathCurrentOffset += m_dy - kerning;
                m_dy = 0;

                // Apply dx/dy correction and setup translations that move to the glyph midpoint.
                xOrientationShift += m_dx + baselineShift;
                yOrientationShift -= scaledGlyphAdvance / 2;
            } else {
                // If there's an absolute x position available, it marks the beginning of a new position along the path.
                if (x != SVGTextLayoutAttributes::emptyValue())
                    m_textPathCurrentOffset = x + m_textPathStartOffset;

                m_textPathCurrentOffset += m_dx - kerning;
                m_dx = 0;

                // Apply dx/dy correction and setup translations that move to the glyph midpoint.
                xOrientationShift -= scaledGlyphAdvance / 2;
                yOrientationShift += m_dy - baselineShift;
            }

            // Calculate current offset along path.
            textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2;

            // Move to next character.
            m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling;

            // Skip character, if we're before the path.
            if (textPathOffset < 0) {
                advanceToNextLogicalCharacter(logicalMetrics);
                advanceToNextVisualCharacter(visualMetrics);
                continue;
            }

            // Stop processing, if the next character lies behind the path.
            if (textPathOffset > m_textPathLength)
                break;

            bool ok = false;
            FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok);
            ASSERT(ok);

            x = point.x();
            y = point.y();
            angle = m_textPath.normalAngleAtLength(textPathOffset, ok);
            ASSERT(ok);

            // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
            if (m_isVerticalText)
                angle -= 90;
        } else {
            // Apply all previously calculated shift values.
            if (m_isVerticalText) {
                x += baselineShift;
                y -= kerning;
            } else {
                x -= kerning;
                y -= baselineShift;
            }

            x += m_dx;
            y += m_dy;
        }

        // Determine whether we have to start a new fragment.
        bool shouldStartNewFragment = m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle
            || orientationAngle || kerning || applySpacingToNextCharacter || definesTextLength;

        // If we already started a fragment, close it now.
        if (didStartTextFragment && shouldStartNewFragment) {
            applySpacingToNextCharacter = false;
            recordTextFragment(textBox, visualMetricsValues);
        }

        // Eventually start a new fragment, if not yet done.
        if (!didStartTextFragment || shouldStartNewFragment) {
            ASSERT(!m_currentTextFragment.characterOffset);
            ASSERT(!m_currentTextFragment.length);

            didStartTextFragment = true;
            m_currentTextFragment.characterOffset = m_visualCharacterOffset;
            m_currentTextFragment.metricsListOffset = m_visualMetricsListOffset;
            m_currentTextFragment.x = x;
            m_currentTextFragment.y = y;

            // Build fragment transformation.
            if (angle)
                m_currentTextFragment.transform.rotate(angle);

            if (xOrientationShift || yOrientationShift)
                m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift);

            if (orientationAngle)
                m_currentTextFragment.transform.rotate(orientationAngle);

            m_currentTextFragment.isTextOnPath = m_inPathLayout && m_textPathScaling != 1;
            if (m_currentTextFragment.isTextOnPath) {
                if (m_isVerticalText)
                    m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(1, m_textPathScaling);
                else
                    m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(m_textPathScaling, 1);
            }
        }

        // Update current text position, after processing of the current character finished.
        if (m_inPathLayout)
            updateCurrentTextPosition(x, y, glyphAdvance);
        else {
            // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed.
            if (spacing)
                applySpacingToNextCharacter = true;

            float xNew = x - m_dx;
            float yNew = y - m_dy;

            if (m_isVerticalText)
                xNew -= baselineShift;
            else
                yNew += baselineShift;

            updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing);
        }

        advanceToNextLogicalCharacter(logicalMetrics);
        advanceToNextVisualCharacter(visualMetrics);
        lastAngle = angle;
    }

    if (!didStartTextFragment)
        return;

    // Close last open fragment, if needed.
    recordTextFragment(textBox, visualMetricsValues);
}
Пример #14
0
	void TextWriter::writeln(const char32 ch)
	{
		pImpl->write(StringView(&ch, 1));

		pImpl->writeNewLine();
	}
Пример #15
0
HRESULT WINAPI DDStringView(DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, 
                            char *pResult, size_t max, DWORD reserved)
{
	return StringView(dwAddress, pHelper, nBase, bUniStrings, pResult, max, 4);
}
Пример #16
0
 StringView getKeywordName(Keyword keyword) {
     return StringView(keywordNames[static_cast<std::size_t>(keyword)]);
 }
Пример #17
0
 StringView getSimpleTokenName(Token token) {
     return StringView(tokenNames[static_cast<std::size_t>(token.type)]);
 }
Пример #18
0
bool SVGViewSpec::parseViewSpec(const String& viewSpec)
{
    auto upconvertedCharacters = StringView(viewSpec).upconvertedCharacters();
    const UChar* currViewSpec = upconvertedCharacters;
    const UChar* end = currViewSpec + viewSpec.length();

    if (currViewSpec >= end || !m_contextElement)
        return false;

    if (!skipString(currViewSpec, end, svgViewSpec, WTF_ARRAY_LENGTH(svgViewSpec)))
        return false;

    if (currViewSpec >= end || *currViewSpec != '(')
        return false;
    currViewSpec++;

    while (currViewSpec < end && *currViewSpec != ')') {
        if (*currViewSpec == 'v') {
            if (skipString(currViewSpec, end, viewBoxSpec, WTF_ARRAY_LENGTH(viewBoxSpec))) {
                if (currViewSpec >= end || *currViewSpec != '(')
                    return false;
                currViewSpec++;
                FloatRect viewBox;
                if (!SVGFitToViewBox::parseViewBox(&m_contextElement->document(), currViewSpec, end, viewBox, false))
                    return false;
                setViewBoxBaseValue(viewBox);
                if (currViewSpec >= end || *currViewSpec != ')')
                    return false;
                currViewSpec++;
            } else if (skipString(currViewSpec, end, viewTargetSpec, WTF_ARRAY_LENGTH(viewTargetSpec))) {
                if (currViewSpec >= end || *currViewSpec != '(')
                    return false;
                const UChar* viewTargetStart = ++currViewSpec;
                while (currViewSpec < end && *currViewSpec != ')')
                    currViewSpec++;
                if (currViewSpec >= end)
                    return false;
                setViewTargetString(String(viewTargetStart, currViewSpec - viewTargetStart));
                currViewSpec++;
            } else
                return false;
        } else if (*currViewSpec == 'z') {
            if (!skipString(currViewSpec, end, zoomAndPanSpec, WTF_ARRAY_LENGTH(zoomAndPanSpec)))
                return false;
            if (currViewSpec >= end || *currViewSpec != '(')
                return false;
            currViewSpec++;
            if (!parse(currViewSpec, end, m_zoomAndPan))
                return false;
            if (currViewSpec >= end || *currViewSpec != ')')
                return false;
            currViewSpec++;
        } else if (*currViewSpec == 'p') {
            if (!skipString(currViewSpec, end, preserveAspectRatioSpec, WTF_ARRAY_LENGTH(preserveAspectRatioSpec)))
                return false;
            if (currViewSpec >= end || *currViewSpec != '(')
                return false;
            currViewSpec++;
            SVGPreserveAspectRatio preserveAspectRatio;
            if (!preserveAspectRatio.parse(currViewSpec, end, false))
                return false;
            setPreserveAspectRatioBaseValue(preserveAspectRatio);
            if (currViewSpec >= end || *currViewSpec != ')')
                return false;
            currViewSpec++;
        } else if (*currViewSpec == 't') {
            if (!skipString(currViewSpec, end, transformSpec, WTF_ARRAY_LENGTH(transformSpec)))
                return false;
            if (currViewSpec >= end || *currViewSpec != '(')
                return false;
            currViewSpec++;
            SVGTransformable::parseTransformAttribute(m_transform, currViewSpec, end, SVGTransformable::DoNotClearList);
            if (currViewSpec >= end || *currViewSpec != ')')
                return false;
            currViewSpec++;
        } else
            return false;

        if (currViewSpec < end && *currViewSpec == ';')
            currViewSpec++;
    }
    
    if (currViewSpec >= end || *currViewSpec != ')')
        return false;

    return true;
}
Пример #19
0
static String truncateString(const String& string, float maxWidth, const FontCascade& font, TruncationFunction truncateToBuffer, bool disableRoundingHacks, float* resultWidth = nullptr, bool shouldInsertEllipsis = true,  float customTruncationElementWidth = 0, bool alwaysTruncate = false)
{
    if (string.isEmpty())
        return string;

    if (resultWidth)
        *resultWidth = 0;

    ASSERT(maxWidth >= 0);

    float currentEllipsisWidth = shouldInsertEllipsis ? stringWidth(font, &horizontalEllipsis, 1, disableRoundingHacks) : customTruncationElementWidth;

    UChar stringBuffer[STRING_BUFFER_SIZE];
    unsigned truncatedLength;
    unsigned keepCount;
    unsigned length = string.length();

    if (length > STRING_BUFFER_SIZE) {
        if (shouldInsertEllipsis)
            keepCount = STRING_BUFFER_SIZE - 1; // need 1 character for the ellipsis
        else
            keepCount = 0;
        truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer, shouldInsertEllipsis);
    } else {
        keepCount = length;
        StringView(string).getCharactersWithUpconvert(stringBuffer);
        truncatedLength = length;
    }

    float width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks);
    if (!shouldInsertEllipsis && alwaysTruncate)
        width += customTruncationElementWidth;
    if ((width - maxWidth) < 0.0001) { // Ignore rounding errors.
        if (resultWidth)
            *resultWidth = width;
        return string;
    }

    unsigned keepCountForLargestKnownToFit = 0;
    float widthForLargestKnownToFit = currentEllipsisWidth;
    
    unsigned keepCountForSmallestKnownToNotFit = keepCount;
    float widthForSmallestKnownToNotFit = width;
    
    if (currentEllipsisWidth >= maxWidth) {
        keepCountForLargestKnownToFit = 1;
        keepCountForSmallestKnownToNotFit = 2;
    }
    
    while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) {
        ASSERT_WITH_SECURITY_IMPLICATION(widthForLargestKnownToFit <= maxWidth);
        ASSERT_WITH_SECURITY_IMPLICATION(widthForSmallestKnownToNotFit > maxWidth);

        float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit)
            / (widthForSmallestKnownToNotFit - widthForLargestKnownToFit);
        keepCount = static_cast<unsigned>(maxWidth * ratio);
        
        if (keepCount <= keepCountForLargestKnownToFit)
            keepCount = keepCountForLargestKnownToFit + 1;
        else if (keepCount >= keepCountForSmallestKnownToNotFit)
            keepCount = keepCountForSmallestKnownToNotFit - 1;
        
        ASSERT_WITH_SECURITY_IMPLICATION(keepCount < length);
        ASSERT(keepCount > 0);
        ASSERT_WITH_SECURITY_IMPLICATION(keepCount < keepCountForSmallestKnownToNotFit);
        ASSERT_WITH_SECURITY_IMPLICATION(keepCount > keepCountForLargestKnownToFit);

        truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer, shouldInsertEllipsis);

        width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks);
        if (!shouldInsertEllipsis)
            width += customTruncationElementWidth;
        if (width <= maxWidth) {
            keepCountForLargestKnownToFit = keepCount;
            widthForLargestKnownToFit = width;
            if (resultWidth)
                *resultWidth = width;
        } else {
            keepCountForSmallestKnownToNotFit = keepCount;
            widthForSmallestKnownToNotFit = width;
        }
    }
    
    if (keepCountForLargestKnownToFit == 0) {
        keepCountForLargestKnownToFit = 1;
    }
    
    if (keepCount != keepCountForLargestKnownToFit) {
        keepCount = keepCountForLargestKnownToFit;
        truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer, shouldInsertEllipsis);
    }
    
    return String(stringBuffer, truncatedLength);
}
Пример #20
0
bool SVGFitToViewBox::parseViewBox(Document* doc, const String& s, FloatRect& viewBox)
{
    auto upconvertedCharacters = StringView(s).upconvertedCharacters();
    const UChar* characters = upconvertedCharacters;
    return parseViewBox(doc, characters, characters + s.length(), viewBox, true);
}
void ThreadableWebSocketChannelClientWrapper::setSubprotocol(const String& subprotocol)
{
    unsigned length = subprotocol.length();
    m_subprotocol.resize(length);
    StringView(subprotocol).getCharactersWithUpconvert(m_subprotocol.data());
}
Пример #22
0
static void appendDescriptorAndReset(const CharType*& descriptorStart, const CharType* position, Vector<StringView>& descriptors)
{
    if (position > descriptorStart)
        descriptors.append(StringView(descriptorStart, position - descriptorStart));
    descriptorStart = nullptr;
}
Пример #23
0
float StringTruncator::width(const String& string, const FontCascade& font, EnableRoundingHacksOrNot enableRoundingHacks)
{
    return stringWidth(font, StringView(string).upconvertedCharacters(), string.length(), !enableRoundingHacks);
}
Пример #24
0
StringView StringView::unhead(Size headSize) const {
  assert(data != nullptr || size == 0);
  assert(headSize <= size);
  return StringView(data + headSize, size - headSize);
}
Пример #25
0
StringView StringView::untail(Size tailSize) const {
  assert(data != nullptr || size == 0);
  assert(tailSize <= size);
  return StringView(data, size - tailSize);
}
Пример #26
0
void IntlNumberFormat::initializeNumberFormat(ExecState& state, JSValue locales, JSValue optionsValue)
{
    VM& vm = state.vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    // 11.1.2 InitializeNumberFormat (numberFormat, locales, options) (ECMA-402)
    // https://tc39.github.io/ecma402/#sec-initializenumberformat

    auto requestedLocales = canonicalizeLocaleList(state, locales);
    RETURN_IF_EXCEPTION(scope, void());

    JSObject* options;
    if (optionsValue.isUndefined())
        options = constructEmptyObject(&state, state.lexicalGlobalObject()->nullPrototypeObjectStructure());
    else {
        options = optionsValue.toObject(&state);
        RETURN_IF_EXCEPTION(scope, void());
    }

    HashMap<String, String> opt;

    String matcher = intlStringOption(state, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit");
    RETURN_IF_EXCEPTION(scope, void());
    opt.add("localeMatcher"_s, matcher);

    auto& availableLocales = state.jsCallee()->globalObject(vm)->intlNumberFormatAvailableLocales();
    auto result = resolveLocale(state, availableLocales, requestedLocales, opt, relevantNumberExtensionKeys, WTF_ARRAY_LENGTH(relevantNumberExtensionKeys), IntlNFInternal::localeData);

    m_locale = result.get("locale"_s);
    if (m_locale.isEmpty()) {
        throwTypeError(&state, scope, "failed to initialize NumberFormat due to invalid locale"_s);
        return;
    }

    m_numberingSystem = result.get("nu"_s);

    String styleString = intlStringOption(state, options, Identifier::fromString(&vm, "style"), { "decimal", "percent", "currency" }, "style must be either \"decimal\", \"percent\", or \"currency\"", "decimal");
    RETURN_IF_EXCEPTION(scope, void());
    if (styleString == "decimal")
        m_style = Style::Decimal;
    else if (styleString == "percent")
        m_style = Style::Percent;
    else if (styleString == "currency")
        m_style = Style::Currency;
    else
        ASSERT_NOT_REACHED();

    String currency = intlStringOption(state, options, Identifier::fromString(&vm, "currency"), { }, nullptr, nullptr);
    RETURN_IF_EXCEPTION(scope, void());
    if (!currency.isNull()) {
        if (currency.length() != 3 || !currency.isAllSpecialCharacters<isASCIIAlpha>()) {
            throwException(&state, scope, createRangeError(&state, "currency is not a well-formed currency code"_s));
            return;
        }
    }

    unsigned currencyDigits = 0;
    if (m_style == Style::Currency) {
        if (currency.isNull()) {
            throwTypeError(&state, scope, "currency must be a string"_s);
            return;
        }

        currency = currency.convertToASCIIUppercase();
        m_currency = currency;
        currencyDigits = computeCurrencyDigits(currency);
    }

    String currencyDisplayString = intlStringOption(state, options, Identifier::fromString(&vm, "currencyDisplay"), { "code", "symbol", "name" }, "currencyDisplay must be either \"code\", \"symbol\", or \"name\"", "symbol");
    RETURN_IF_EXCEPTION(scope, void());
    if (m_style == Style::Currency) {
        if (currencyDisplayString == "code")
            m_currencyDisplay = CurrencyDisplay::Code;
        else if (currencyDisplayString == "symbol")
            m_currencyDisplay = CurrencyDisplay::Symbol;
        else if (currencyDisplayString == "name")
            m_currencyDisplay = CurrencyDisplay::Name;
        else
            ASSERT_NOT_REACHED();
    }

    unsigned minimumIntegerDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumIntegerDigits"), 1, 21, 1);
    RETURN_IF_EXCEPTION(scope, void());
    m_minimumIntegerDigits = minimumIntegerDigits;

    unsigned minimumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : 0;

    unsigned minimumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumFractionDigits"), 0, 20, minimumFractionDigitsDefault);
    RETURN_IF_EXCEPTION(scope, void());
    m_minimumFractionDigits = minimumFractionDigits;

    unsigned maximumFractionDigitsDefault;
    if (m_style == Style::Currency)
        maximumFractionDigitsDefault = std::max(minimumFractionDigits, currencyDigits);
    else if (m_style == Style::Percent)
        maximumFractionDigitsDefault = minimumFractionDigits;
    else
        maximumFractionDigitsDefault = std::max(minimumFractionDigits, 3u);

    unsigned maximumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "maximumFractionDigits"), minimumFractionDigits, 20, maximumFractionDigitsDefault);
    RETURN_IF_EXCEPTION(scope, void());
    m_maximumFractionDigits = maximumFractionDigits;

    JSValue minimumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "minimumSignificantDigits"));
    RETURN_IF_EXCEPTION(scope, void());

    JSValue maximumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "maximumSignificantDigits"));
    RETURN_IF_EXCEPTION(scope, void());

    if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) {
        unsigned minimumSignificantDigits = intlDefaultNumberOption(state, minimumSignificantDigitsValue, Identifier::fromString(&vm, "minimumSignificantDigits"), 1, 21, 1);
        RETURN_IF_EXCEPTION(scope, void());
        unsigned maximumSignificantDigits = intlDefaultNumberOption(state, maximumSignificantDigitsValue, Identifier::fromString(&vm, "maximumSignificantDigits"), minimumSignificantDigits, 21, 21);
        RETURN_IF_EXCEPTION(scope, void());
        m_minimumSignificantDigits = minimumSignificantDigits;
        m_maximumSignificantDigits = maximumSignificantDigits;
    }

    bool usesFallback;
    bool useGrouping = intlBooleanOption(state, options, Identifier::fromString(&vm, "useGrouping"), usesFallback);
    if (usesFallback)
        useGrouping = true;
    RETURN_IF_EXCEPTION(scope, void());
    m_useGrouping = useGrouping;

    UNumberFormatStyle style = UNUM_DEFAULT;
    switch (m_style) {
    case Style::Decimal:
        style = UNUM_DECIMAL;
        break;
    case Style::Percent:
        style = UNUM_PERCENT;
        break;
    case Style::Currency:
        switch (m_currencyDisplay) {
        case CurrencyDisplay::Code:
            style = UNUM_CURRENCY_ISO;
            break;
        case CurrencyDisplay::Symbol:
            style = UNUM_CURRENCY;
            break;
        case CurrencyDisplay::Name:
            style = UNUM_CURRENCY_PLURAL;
            break;
        default:
            ASSERT_NOT_REACHED();
        }
        break;
    default:
        ASSERT_NOT_REACHED();
    }

    UErrorCode status = U_ZERO_ERROR;
    m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(style, nullptr, 0, m_locale.utf8().data(), nullptr, &status));
    if (U_FAILURE(status)) {
        throwTypeError(&state, scope, "failed to initialize NumberFormat"_s);
        return;
    }

    if (m_style == Style::Currency) {
        unum_setTextAttribute(m_numberFormat.get(), UNUM_CURRENCY_CODE, StringView(m_currency).upconvertedCharacters(), m_currency.length(), &status);
        if (U_FAILURE(status)) {
            throwTypeError(&state, scope, "failed to initialize NumberFormat"_s);
            return;
        }
    }
    if (!m_minimumSignificantDigits) {
        unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits);
        unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits);
        unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits);
    } else {
        unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true);
        unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits);
        unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits);
    }
    unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, m_useGrouping);
    unum_setAttribute(m_numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);

    m_initializedNumberFormat = true;
}
Пример #27
0
StringView StringView::range(Size startIndex, Size endIndex) const {
  assert(data != nullptr || size == 0);
  assert(startIndex <= endIndex && endIndex <= size);
  return StringView(data + startIndex, endIndex - startIndex);
}
void ThreadableWebSocketChannelClientWrapper::setExtensions(const String& extensions)
{
    unsigned length = extensions.length();
    m_extensions.resize(length);
    StringView(extensions).getCharactersWithUpconvert(m_extensions.data());
}
Пример #29
0
    Token Scanner::next() {
        std::string text;
        while (true) {
            while (position < buffer.length()) {
                char c = buffer[position];
                switch (state) {
                    case State::Start:
                        switch (c) {
                            case '0':
                                state = State::LeadingZero;
                                text += c;
                                break;
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                state = State::IntegerDigits;
                                text += c;
                                break;
                            case '_':
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                            case 'g':
                            case 'h':
                            case 'i':
                            case 'j':
                            case 'k':
                            case 'l':
                            case 'm':
                            case 'n':
                            case 'o':
                            case 'p':
                            case 'q':
                            case 'r':
                            case 's':
                            case 't':
                            case 'u':
                            case 'v':
                            case 'w':
                            case 'x':
                            case 'y':
                            case 'z':
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                            case 'G':
                            case 'H':
                            case 'I':
                            case 'J':
                            case 'K':
                            case 'L':
                            case 'M':
                            case 'N':
                            case 'O':
                            case 'P':
                            case 'Q':
                            case 'R':
                            case 'S':
                            case 'T':
                            case 'U':
                            case 'V':
                            case 'W':
                            case 'X':
                            case 'Y':
                            case 'Z':
                                state = State::Identifier;
                                text += c;
                                break;
                            case '\'': case '\"':
                                terminator = c;
                                state = State::String;
                                break;
                            case ' ': case '\t': case '\r': case '\n': break;
                            case ':': position++; return Token(TokenType::Colon);
                            case ',': position++; return Token(TokenType::Comma);
                            case '.': state = State::Dot; break;
                            case '(': position++; return Token(TokenType::LeftParenthesis);
                            case ')': position++; return Token(TokenType::RightParenthesis);
                            case '[': position++; return Token(TokenType::LeftBracket);
                            case ']': position++; return Token(TokenType::RightBracket);
                            case '{': position++; return Token(TokenType::LeftBrace);
                            case '}': position++; return Token(TokenType::RightBrace);
                            case ';': position++; return Token(TokenType::Semicolon);
                            case '#': state = State::Hash; break;
                            case '|': state = State::Bar; break;
                            case '*': state = State::Asterisk; break;
                            case '%': state = State::Percent; break;
                            case '@': position++; return Token(TokenType::At);
                            case '&': state = State::Ampersand; break;
                            case '^': state = State::Caret; break;
                            case '!': state = State::Exclamation; break;
                            case '`': position++; return Token(TokenType::Backtick);
                            case '~': position++; return Token(TokenType::Tilde);
                            case '+': state = State::Plus; break;
                            case '-': state = State::Minus; break;
                            case '/': state = State::Slash; break;
                            case '=': state = State::Equals; break;
                            case '<': state = State::LessThan; break;
                            case '>': state = State::GreaterThan; break;
                            case '$': position++; return Token(TokenType::Dollar);
                            default:
                                report->error("unrecognized character `\\" + std::string(1, c) + "` found", location);
                                break;
                        }
                        break;
                    case State::Identifier:
                        switch (c) {
                            case '_':
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                            case 'g':
                            case 'h':
                            case 'i':
                            case 'j':
                            case 'k':
                            case 'l':
                            case 'm':
                            case 'n':
                            case 'o':
                            case 'p':
                            case 'q':
                            case 'r':
                            case 's':
                            case 't':
                            case 'u':
                            case 'v':
                            case 'w':
                            case 'x':
                            case 'y':
                            case 'z':
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                            case 'G':
                            case 'H':
                            case 'I':
                            case 'J':
                            case 'K':
                            case 'L':
                            case 'M':
                            case 'N':
                            case 'O':
                            case 'P':
                            case 'Q':
                            case 'R':
                            case 'S':
                            case 'T':
                            case 'U':
                            case 'V':
                            case 'W':
                            case 'X':
                            case 'Y':
                            case 'Z':
                                text += c;
                                break;
                            default: {
                                state = State::Start;
                                const auto internedText = stringPool->intern(text);
                                return Token(TokenType::Identifier, findKeyword(internedText), internedText);
                            }
                        }
                        break;
                    case State::String:
                        if (c == terminator) {
                            position++;
                            state = State::Start;
                            switch (terminator) {
                                case '\'':
                                    if (text.size() != 1) {
                                        report->error("invalid character literal '" + text::escape(StringView(text), '\'') + "' (character literals must be exactly one character)", location);
                                        return Token(TokenType::Character, StringView(ErrorText));
                                    } else {
                                        return Token(TokenType::Character, stringPool->intern(text));
                                    }
                                default: return Token(TokenType::String, stringPool->intern(text));
                            }
                        } else switch (c) {
                            case '\\':
                                state = State::StringEscape;
                                break;
                            default:
                                text += c;
                                break;
                        }
                        break;
                    case State::StringEscape:
                        state = State::String;
                        switch (c) {
                            case '\"': case '\'': case '\\':
                                text += c;
                                break;
                            case 't': text += '\t'; break;
                            case 'r': text += '\r'; break;
                            case 'n': text += '\n'; break;
                            case 'f': text += '\f'; break;
                            case 'b': text += '\b'; break;
                            case 'a': text += '\a'; break;
                            case '0': text += '\0'; break;
                            case 'x':
                                state = State::HexEscapeFirstDigit;
                                break;
                            default:
                                report->error("invalid escape sequence `\\" + std::string(1, c) + "` in quoted literal", location);
                                break;
                        }
                        break;
                    case State::HexEscapeFirstDigit: {
                        state = State::HexEscapeSecondDigit;
                        switch (c) {
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                intermediateCharCode = static_cast<std::uint8_t>(c - '0') << 4;
                                break;
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                                intermediateCharCode = static_cast<std::uint8_t>(c - 'a' + 10) << 4;
                                break;
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                                intermediateCharCode = static_cast<std::uint8_t>(c - 'A' + 10) << 4;
                                break;
                            default:
                                state = State::String;
                                report->error("hex escape `\\x` contains illegal character `" + std::string(1, c) + "`", location);
                                break;
                        }
                        break;
                    }
                    case State::HexEscapeSecondDigit: {
                        state = State::String;
                        switch (c) {
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                text += static_cast<char>(intermediateCharCode | static_cast<std::uint8_t>(c - '0'));
                                break;
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                                text += static_cast<char>(intermediateCharCode | static_cast<std::uint8_t>(c - 'a' + 10));
                                break;
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                                text += static_cast<char>(intermediateCharCode | static_cast<std::uint8_t>(c - 'A' + 10));
                                break;
                            default:
                                state = State::String;
                                report->error("hex escape `\\x` contains illegal character `" + std::string(1, c) + "`", location);
                                break;
                        }
                        break;
                    }
                    case State::LeadingZero:
                        switch (c) {
                            case '_':
                                state = State::IntegerDigits;
                                break;
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                state = State::IntegerDigits;
                                text += c;
                                break;
                            case 'x': state = State::HexadecimalDigits; text += c; break;
                            case 'b': state = State::BinaryDigits; text += c; break;
                            case 'o': state = State::OctalDigits; text += c; break;
                            case 'u': case 'i':
                                text += c;
                                baseTokenType = TokenType::Integer;
                                state = State::LiteralSuffix;
                                break;
                            default:
                                state = State::Start;
                                return Token(TokenType::Integer, stringPool->intern(text));
                        }
                        break;
                    case State::IntegerDigits:
                        switch (c) {
                            case '_':
                                break;
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                text += c;
                                break;
                            case 'u': case 'i':
                                text += c;
                                baseTokenType = TokenType::Integer;
                                state = State::LiteralSuffix;
                                break;
                            default:
                                state = State::Start;
                                return Token(TokenType::Integer, stringPool->intern(text));
                        }
                        break;
                    case State::HexadecimalDigits:
                        switch (c) {
                            case '_':
                                break;
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                                text += c;
                                break;
                            case 'u': case 'i':
                                text += c;
                                baseTokenType = TokenType::Hexadecimal;
                                state = State::LiteralSuffix;
                                break;
                            default:
                                state = State::Start;
                                return Token(TokenType::Hexadecimal, stringPool->intern(text));
                        }
                        break;
                    case State::OctalDigits:
                        switch (c) {
                            case '_':
                                break;
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                                text += c;
                                break;
                            case 'u': case 'i':
                                text += c;
                                baseTokenType = TokenType::Octal;
                                state = State::LiteralSuffix;
                                break;
                            default:
                                state = State::Start;
                                return Token(TokenType::Octal, stringPool->intern(text));
                        }
                        break;
                    case State::BinaryDigits:
                        switch (c) {
                            case '_':
                                break;
                            case '0': case '1':
                                text += c;
                                break;
                            case 'u': case 'i':
                                text += c;
                                baseTokenType = TokenType::Binary;
                                state = State::LiteralSuffix;
                                break;
                            default:
                                state = State::Start;
                                return Token(TokenType::Binary, stringPool->intern(text));
                        }
                        break;
                    case State::LiteralSuffix:
                        switch (c) {
                            case '_':
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                            case 'g':
                            case 'h':
                            case 'i':
                            case 'j':
                            case 'k':
                            case 'l':
                            case 'm':
                            case 'n':
                            case 'o':
                            case 'p':
                            case 'q':
                            case 'r':
                            case 's':
                            case 't':
                            case 'u':
                            case 'v':
                            case 'w':
                            case 'x':
                            case 'y':
                            case 'z':
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                            case 'G':
                            case 'H':
                            case 'I':
                            case 'J':
                            case 'K':
                            case 'L':
                            case 'M':
                            case 'N':
                            case 'O':
                            case 'P':
                            case 'Q':
                            case 'R':
                            case 'S':
                            case 'T':
                            case 'U':
                            case 'V':
                            case 'W':
                            case 'X':
                            case 'Y':
                            case 'Z':
                                text += c;
                                break;
                            default: {
                                state = State::Start;
                                return Token(baseTokenType, Keyword::None, stringPool->intern(text));
                            }
                        }
                        break;
                    case State::Exclamation:
                        state = State::Start;
                        switch (c) {
                            case '=': position++; return Token(TokenType::ExclamationEquals);
                            default: return Token(TokenType::Exclamation);
                        }
                        break;
                    case State::Plus:
                        state = State::Start;
                        switch (c) {
                            case '#': state = State::PlusHash; break;
                            case '+': position++; return Token(TokenType::DoublePlus);
                            case '=': position++; return Token(TokenType::PlusEquals);
                            default: return Token(TokenType::Plus);
                        }
                        break;
                    case State::PlusHash:
                        state = State::Start;
                        switch (c) {
                            case '=': position++; return Token(TokenType::PlusHashEquals);
                            default: return Token(TokenType::PlusHash);
                        }
                        break;
                    case State::Minus:
                        state = State::Start;
                        switch (c) {
                            case '#': state = State::MinusHash; break;
                            case '-': position++; return Token(TokenType::DoubleMinus);
                            case '=': position++; return Token(TokenType::MinusEquals);
                            case '0':
                                state = State::LeadingZero;
                                text += '-';
                                text += c;
                                break;
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                state = State::IntegerDigits;
                                text += '-';
                                text += c;
                                break;
                            default: return Token(TokenType::Minus);
                        }
                        break;
                    case State::MinusHash:
                        state = State::Start;
                        switch (c) {
                            case '=': position++; return Token(TokenType::MinusHashEquals);
                            default: return Token(TokenType::MinusHash);
                        }
                        break;
                    case State::Asterisk:
                        state = State::Start;
                        switch (c) {
                            case '=': position++; return Token(TokenType::AsteriskEquals);
                            default: return Token(TokenType::Asterisk);
                        }
                        break;
                    case State::Percent:
                        state = State::Start;
                        switch (c) {
                            case '=': position++; return Token(TokenType::PercentEquals);
                            default: return Token(TokenType::Percent);
                        }
                        break;
                    case State::Slash:
                        state = State::Start;
                        switch (c) {
                            case '/':
                                state = State::DoubleSlashComment;
                                break;
                            case '*':
                                state = State::SlashStarComment;
                                commentStartLocation.line = location.line;
                                break;
                            case '=': position++; return Token(TokenType::SlashEquals);
                            default:
                                return Token(TokenType::Slash);
                        }
                        break;
                    case State::DoubleSlashComment: break;
                    case State::SlashStarComment:
                        switch (c) {
                            case '*': state = State::SlashStarCommentStar;
                            default: break;
                        }
                        break;
                    case State::SlashStarCommentStar:
                        switch (c) {
                            case '/': state = State::Start; break;
                            default: state = State::SlashStarComment; break;
                        }
                        break;
                    case State::Equals:
                        state = State::Start;
                        switch (c) {
                            case '=': position++; return Token(TokenType::DoubleEquals);
                            default: return Token(TokenType::Equals);
                        }
                        break;
                    case State::Ampersand:
                        state = State::Start;
                        switch (c) {
                            case '&': position++; return Token(TokenType::DoubleAmpersand);
                            case '=': position++; return Token(TokenType::AmpersandEquals);
                            default: return Token(TokenType::Ampersand);
                        }
                        break;
                    case State::Bar:
                        state = State::Start;
                        switch (c) {
                            case '|': position++; return Token(TokenType::DoubleBar);
                            case '=': position++; return Token(TokenType::BarEquals);
                            default: return Token(TokenType::Bar);
                        }
                        break;
                    case State::Caret:
                        state = State::Start;
                        switch (c) {
                            case '=': position++; return Token(TokenType::CaretEquals);
                            default: return Token(TokenType::Caret);
                        }
                        break;
                    case State::LessThan:
                        state = State::Start;
                        switch (c) {
                            case '<': state = State::DoubleLessThan; break;
                            case '=': position++; return Token(TokenType::LessThanEquals);
                            case ':': position++; return Token(TokenType::LessColon);
                            default: return Token(TokenType::LessThan);
                        }
                        break;
                    case State::DoubleLessThan:
                        state = State::Start;
                        switch (c) {
                            case '<': state = State::TripleLessThan; break;
                            case '=': position++; return Token(TokenType::DoubleLessThanEquals);
                            default: return Token(TokenType::DoubleLessThan);
                        }
                        break;
                    case State::TripleLessThan:
                        state = State::Start;
                        switch (c) {
                            case '<': state = State::QuadrupleLessThan; break;
                            case '=': position++; return Token(TokenType::TripleLessThanEquals);
                            default: return Token(TokenType::TripleLessThan);
                        }
                        break;
                    case State::QuadrupleLessThan:
                        state = State::Start;
                        switch (c) {
                            case '#': state = State::QuadrupleLessThanHash; break;
                            case '=': position++; return Token(TokenType::QuadrupleLessThanEquals);
                            default: return Token(TokenType::QuadrupleLessThan);
                        }
                        break;
                    case State::QuadrupleLessThanHash:
                        state = State::Start;
                        switch (c) {                            
                            case '=': position++; return Token(TokenType::QuadrupleLessThanHashEquals);
                            default: return Token(TokenType::QuadrupleLessThanHash);
                        }
                        break;
                    case State::GreaterThan:
                        state = State::Start;
                        switch (c) {
                            case '>': state = State::DoubleGreaterThan; break;
                            case '=': position++; return Token(TokenType::GreaterThanEquals);
                            case ':': position++; return Token(TokenType::GreaterColon);
                            default: return Token(TokenType::GreaterThan);
                        }
                        break;
                    case State::DoubleGreaterThan:
                        state = State::Start;
                        switch (c) {
                            case '>': state = State::TripleGreaterThan; break;
                            case '=': position++; return Token(TokenType::DoubleGreaterThanEquals);
                            default: return Token(TokenType::DoubleGreaterThan);
                        }
                        break;
                    case State::TripleGreaterThan:
                        state = State::Start;
                        switch (c) {
                            case '>': state = State::QuadrupleGreaterThan; break;
                            case '=': position++; return Token(TokenType::TripleGreaterThanEquals);
                            default: return Token(TokenType::TripleGreaterThan);
                        }
                        break;
                    case State::QuadrupleGreaterThan:
                        state = State::Start;
                        switch (c) {
                            case '#': state = State::QuadrupleGreaterThanHash; break;
                            case '=': position++; return Token(TokenType::QuadrupleGreaterThanEquals);
                            default: return Token(TokenType::QuadrupleGreaterThan);
                        }
                        break;
                    case State::QuadrupleGreaterThanHash:
                        state = State::Start;
                        switch (c) {                            
                            case '=': position++; return Token(TokenType::QuadrupleGreaterThanHashEquals);
                            default: return Token(TokenType::QuadrupleGreaterThanHash);
                        }
                        break;
                    case State::Dot:
                        state = State::Start;
                        switch (c) {
                            case '.': state = State::DoubleDot; break;
                            default: return Token(TokenType::Dot);
                        }
                        break;
                    case State::DoubleDot:
                        state = State::Start;
                        switch (c) {
                            case '.': position++; return Token(TokenType::DotDotDot);
                            default: return Token(TokenType::DotDot);
                        }
                        break;
                    case State::Hash:
                        state = State::Start;
                        switch (c) {
                            case '[': position++; return Token(TokenType::HashBracket);
                            default: return Token(TokenType::Hash);
                        }
                        break;
                }
                position++;
            }

            if (reader && reader->isOpen() && reader->readLine(buffer)) {
                // Special handling in states for end-of-line.
                switch (state) {
                    case State::DoubleSlashComment:
                        state = State::Start;
                        break;
                    case State::String:
                        state = State::Start;
                        report->error("expected closing quote `" + std::string(1, terminator) + "`, but got end-of-line", location);
                        break;
                    case State::StringEscape:
                        state = State::Start;
                        report->error("expected string escape sequence, but got end-of-line", location);
                        break;
                    default:
                        break;
                }
                position = 0;
                location.line++;
            }
            else {
                // End-of-file.
                switch (state) {
                    case State::Identifier: {
                        state = State::Start;
                        const auto internedText = stringPool->intern(text);
                        return Token(TokenType::Identifier, findKeyword(internedText), internedText);
                    }
                    case State::LeadingZero:
                    case State::IntegerDigits:
                        state = State::Start;
                        return Token(TokenType::Integer, stringPool->intern(text));
                    case State::HexadecimalDigits:
                        state = State::Start;
                        return Token(TokenType::Hexadecimal, stringPool->intern(text));
                    case State::BinaryDigits:
                        state = State::Start;
                        return Token(TokenType::Binary, stringPool->intern(text));
                    case State::String:
                        state = State::Start;
                        report->error("expected closing quote `" + std::string(1, terminator) + "`, but got end-of-file", location);
                        break;
                    case State::StringEscape:
                        state = State::Start;
                        report->error("expected string escape sequence, but got end-of-file", location);
                        break;
                    case State::SlashStarComment:
                        report->error("expected `*/` to close comment `/*`, but got end-of-file", location, ReportErrorFlags(ReportErrorFlagType::Continued));
                        report->error("comment `/*` started here", commentStartLocation);
                        break;
                    default:
                        break;
                }
                return Token(TokenType::EndOfFile);
            }
        }
        return Token(TokenType::EndOfFile);
    }