void Quake3MapReader::readFromStream(std::istream& stream)
{
	// Call the virtual method to initialise the primitve parser map (if not done yet)
	initPrimitiveParsers();

	// The tokeniser used to split the stream into pieces
	parser::BasicDefTokeniser<std::istream> tok(stream);

	// Read each entity in the map, until EOF is reached
	while (tok.hasMoreTokens())
	{
		// Create an entity node by parsing from the stream. If there is an
		// exception, display it and return
		try
		{
			parseEntity(tok);
		}
		catch (FailureException& e)
		{
			std::string text = (boost::format(_("Failed parsing entity %d:\n%s")) % _entityCount % e.what()).str();

			// Re-throw with more text
			throw FailureException(text);
		}

		_entityCount++;
	}

	// EOF reached, success
}
Exemplo n.º 2
0
 void MapParser::parseMap(Model::Map& map, Utility::ProgressIndicator* indicator) {
     Model::Entity* entity = NULL;
     
     if (indicator != NULL) indicator->reset(static_cast<int>(m_size));
     try {
         while ((entity = parseEntity(map.worldBounds(), indicator)) != NULL)
             map.addEntity(*entity);
     } catch (MapParserException e) {
         m_console.error(e.what());
     }
     
     if (indicator != NULL)
         indicator->update(static_cast<int>(m_size));
     
     if (!m_staleBrushes.empty()) {
         StringStream stream;
         stream << "Found brushes with invalid or missing geometry at lines ";
         for (size_t i = 0; i < m_staleBrushes.size(); i++) {
             const Model::Brush* brush = m_staleBrushes[i];
             stream << brush->fileLine();
             if (i < m_staleBrushes.size() - 1)
                 stream << ", ";
         }
         m_console.warn(stream.str());
     }
 }
Exemplo n.º 3
0
GraphicEntity::GraphicEntity(int id, std::string type) : Entity(id, type)
{
	parseEntity();
	// TODO : action must be set by logic game module
	Entity::setAction("default");
	// TODO : position must be set by logic game module
	m_position = m_sprite->getCenter();
	m_updated = false;
}
Exemplo n.º 4
0
	Expression* parseParenthesis(TokenStream* tokens, FileContext* fileContext)
	{
		if (tokens->safePeekValue() == "(")
		{
			Token openParen = tokens->pop();
			Expression* expression = parseExpression(tokens, fileContext);
			tokens->popExpected(")");
			return expression;
		}
		return parseEntity(tokens, fileContext);
	}
Exemplo n.º 5
0
 bool MapParser::parseEntities(const BBox& worldBounds, Model::EntityList& entities) {
     size_t oldSize = entities.size();
     try {
         Model::Entity* entity = NULL;
         while ((entity = parseEntity(worldBounds, NULL)) != NULL)
             entities.push_back(entity);
         return !entities.empty();
     } catch (MapParserException e) {
         Utility::deleteAll(entities, oldSize);
         m_tokenizer.reset();
         return false;
     }
 }
Exemplo n.º 6
0
 void MapParser::parseMap(Model::Map& map, Utility::ProgressIndicator* indicator) {
     Model::Entity* entity = NULL;
     
     if (indicator != NULL) indicator->reset(static_cast<int>(m_size));
     try {
         while ((entity = parseEntity(map.worldBounds(), indicator)) != NULL)
             map.addEntity(*entity);
     } catch (MapParserException e) {
         m_console.error(e.what());
     }
     if (indicator != NULL)
         indicator->update(static_cast<int>(m_size));
 }
Exemplo n.º 7
0
/* ****************************************************************************
*
* jsonRequestTreat - 
*/
std::string jsonRequestTreat(ConnectionInfo* ciP, ParseData* parseDataP, RequestType requestType, JsonDelayedRelease* releaseP)
{
  std::string answer;

  switch (requestType)
  {
  case EntitiesRequest:  // POST /v2/entities
    releaseP->entity = &parseDataP->ent.res;
    answer = parseEntity(ciP, &parseDataP->ent.res, false);
    if (answer != "OK")
    {
      return answer;
    }

    if ((answer = parseDataP->ent.res.check(ciP, EntitiesRequest)) != "OK")
    {
      OrionError error(SccBadRequest, "Parse Error (" + answer + ")");
      return error.render(ciP, "");
    }
    break;

  case EntityRequest:  // POST /v2/entities/<eid>
    releaseP->entity = &parseDataP->ent.res;
    answer = parseEntity(ciP, &parseDataP->ent.res, true);
    if (answer != "OK")
    {
      return answer;
    }

    if ((answer = parseDataP->ent.res.check(ciP, EntityRequest)) != "OK")
    {
      OrionError error(SccBadRequest, "Parse Error (" + answer + ")");
      return error.render(ciP, "");
    }
    break;

  case EntityAttributeRequest:
    releaseP->attribute = &parseDataP->attr.attribute;
    answer = parseContextAttribute(ciP, &parseDataP->attr.attribute);
    parseDataP->attr.attribute.present("Parsed Attribute: ", 0);
    if (answer != "OK")
    {
      return answer;
    }
    break;

  case EntityAttributeValueRequest:
    releaseP->attribute = &parseDataP->av.attribute;
    answer = parseAttributeValue(ciP, &parseDataP->av.attribute);
    if (answer != "OK")
    {
      return answer;
    }
    break;

  default:
    answer = "Request Treat function not implemented";
    break;
  }
  
  return answer;
}
/* ****************************************************************************
*
* jsonRequestTreat - 
*/
std::string jsonRequestTreat
(
  ConnectionInfo*            ciP,
  ParseData*                 parseDataP,
  RequestType                requestType,
  JsonDelayedRelease*        releaseP,
  std::vector<std::string>&  compV
)
{
  std::string      answer;
  struct timespec  start;
  struct timespec  end;

  if (timingStatistics)
  {
    clock_gettime(CLOCK_REALTIME, &start);
  }

  switch (requestType)
  {
  case EntitiesRequest:  // POST /v2/entities
    releaseP->entity = &parseDataP->ent.res;
    answer = parseEntity(ciP, &parseDataP->ent.res, false);
    if (answer != "OK")
    {
      return answer;
    }

    if ((answer = parseDataP->ent.res.check(ciP, EntitiesRequest)) != "OK")
    {
      OrionError error(SccBadRequest, answer);
      return error.render(ciP, "");
    }
    break;

  case EntityRequest:  // POST|PUT /v2/entities/<eid>
    releaseP->entity = &parseDataP->ent.res;
    answer = parseEntity(ciP, &parseDataP->ent.res, true);
    if (answer != "OK")
    {
      return answer;
    }

    if ((answer = parseDataP->ent.res.check(ciP, EntityRequest)) != "OK")
    {
      OrionError error(SccBadRequest, answer);
      return error.render(ciP, "");
    }
    break;

  case EntityAttributeRequest:
    releaseP->attribute = &parseDataP->attr.attribute;
    releaseP->attribute->name = compV[4];
    answer = parseContextAttribute(ciP, &parseDataP->attr.attribute);
    if (answer != "OK")
    {
      return answer;
    }

    if ((answer = parseDataP->attr.attribute.check(ciP, EntityAttributeRequest, "", "", 0)) != "OK")
    {
      OrionError error(SccBadRequest, answer);
      return error.render(ciP, "");
    }
    break;

  case EntityAttributeValueRequest:
    releaseP->attribute = &parseDataP->av.attribute;
    answer = parseAttributeValue(ciP, &parseDataP->av.attribute);
    if (answer != "OK")
    {
      return answer;
    }
    break;

  case SubscriptionsRequest:
    answer = parseSubscription(ciP, &parseDataP->subsV2);
    if (answer != "OK")
    {
      return answer;
    }
    break;

  case IndividualSubscriptionRequest:
    answer = parseSubscription(ciP, &parseDataP->subsV2, true);  // NOTE: partial == true
    if (answer != "OK")
    {
      return answer;
    }
    break;

  case BatchQueryRequest:
    answer = parseBatchQuery(ciP, &parseDataP->bq.res);
    if (answer != "OK")
    {
      return answer;
    }
    break;

  case BatchUpdateRequest:
    answer = parseBatchUpdate(ciP, &parseDataP->bu.res);
    if (answer != "OK")
    {
      return answer;
    }

    break;

  default:
    OrionError error(SccNotImplemented, "Request Treat function not implemented");
    answer = error.render(ciP, "");
    ciP->httpStatusCode = SccNotImplemented;
    break;
  }
  
  if (timingStatistics)
  {
    clock_gettime(CLOCK_REALTIME, &end);
    clock_difftime(&end, &start, &threadLastTimeStat.jsonV2ParseTime);
  }

  return answer;
}
void QDeclarativeStyledTextPrivate::parse()
{
    QList<QTextLayout::FormatRange> ranges;
    QStack<QTextCharFormat> formatStack;

    QString drawText;
    drawText.reserve(text.count());

    int textStart = 0;
    int textLength = 0;
    int rangeStart = 0;
    const QChar *ch = text.constData();
    while (!ch->isNull()) {
        if (*ch == lessThan) {
            if (textLength)
                drawText.append(QStringRef(&text, textStart, textLength));
            if (rangeStart != drawText.length() && formatStack.count()) {
                QTextLayout::FormatRange formatRange;
                formatRange.format = formatStack.top();
                formatRange.start = rangeStart;
                formatRange.length = drawText.length() - rangeStart;
                ranges.append(formatRange);
            }
            rangeStart = drawText.length();
            ++ch;
            if (*ch == slash) {
                ++ch;
                if (parseCloseTag(ch, text)) {
                    if (formatStack.count())
                        formatStack.pop();
                }
            } else {
                QTextCharFormat format;
                if (formatStack.count())
                    format = formatStack.top();
                if (parseTag(ch, text, drawText, format))
                    formatStack.push(format);
            }
            textStart = ch - text.constData() + 1;
            textLength = 0;
        } else if (*ch == ampersand) {
            ++ch;
            drawText.append(QStringRef(&text, textStart, textLength));
            parseEntity(ch, text, drawText);
            textStart = ch - text.constData() + 1;
            textLength = 0;
        } else {
            ++textLength;
        }
        if (!ch->isNull())
            ++ch;
    }
    if (textLength)
        drawText.append(QStringRef(&text, textStart, textLength));
    if (rangeStart != drawText.length() && formatStack.count()) {
        QTextLayout::FormatRange formatRange;
        formatRange.format = formatStack.top();
        formatRange.start = rangeStart;
        formatRange.length = drawText.length() - rangeStart;
        ranges.append(formatRange);
    }

    layout.setText(drawText);
    layout.setAdditionalFormats(ranges);
}
Exemplo n.º 10
0
int URI::parseHierarchicalPart(int p0)
{
    int p = p0;
    int ch;

    //# Authority field (host and port, for example)
    int p2 = match(p, "//");
    if (p2 > p)
        {
        p = p2;
        portSpecified = false;
        DOMString portStr;
        while (p < parselen)
            {
            ch = peek(p);
            if (ch == '/')
                break;
            else if (ch == '&') //IRI entity
                {
                int val;
                p2 = parseEntity(p, val);
                if (p2<p)
                    {
                    return -1;
                    }
                p = p2;
                authority.push_back((XMLCh)val);
                }
            else if (ch == '%') //ascii hex excape
                {
                int val;
                p2 = parseAsciiEntity(p, val);
                if (p2<p)
                    {
                    return -1;
                    }
                p = p2;
                authority.push_back((XMLCh)val);
                }
            else if (ch == ':')
                {
                portSpecified = true;
                p++;
                }
            else if (portSpecified)
                {
                portStr.push_back((XMLCh)ch);
                p++;
                }
            else
                {
                authority.push_back((XMLCh)ch);
                p++;
                }
            }
        if (portStr.size() > 0)
            {
            char *pstr = (char *)portStr.c_str();
            char *endStr;
            long val = strtol(pstr, &endStr, 10);
            if (endStr > pstr) //successful parse?
                port = val;
            }
        }

    //# Are we absolute?
    ch = peek(p);
    if (uni_is_letter(ch) && peek(p+1)==':')
        {
        absolute = true;
        path.push_back((XMLCh)'/');
        }
    else if (ch == '/')
        {
        absolute = true;
        if (p>p0) //in other words, if '/' is not the first char
            opaque = true;
        path.push_back((XMLCh)ch);
        p++;
        }

    while (p < parselen)
        {
        ch = peek(p);
        if (ch == '?' || ch == '#')
            break;
        else if (ch == '&') //IRI entity
            {
            int val;
            p2 = parseEntity(p, val);
            if (p2<p)
                {
                return -1;
                }
            p = p2;
            path.push_back((XMLCh)val);
            }
        else if (ch == '%') //ascii hex excape
            {
            int val;
            p2 = parseAsciiEntity(p, val);
            if (p2<p)
                {
                return -1;
                }
            p = p2;
            path.push_back((XMLCh)val);
            }
        else
            {
            path.push_back((XMLCh)ch);
            p++;
            }
        }
    //trace("path:%s", toStr(path).c_str());
    return p;
}
Exemplo n.º 11
0
Node::Node( std::istream &in,  const int style, const int l, const int pos  )
   throw( MalformedError ): Element( l, pos )
{
   // variables to optimize data node promotion in tag nodes
   bool promote_data = true;
   Node *the_data_node = 0;
   char chr;
   std::string entity;
   int iStatus = STATUS_BEGIN;

   m_prev = m_next = m_parent = m_child = m_last_child = 0;
   // defaults to data type: parents will ignore/destroy empty data elements
   m_type = typeData;

   while ( iStatus >= 0 && in.good() ) {
      in.get( chr );
      // resetting new node foundings
      nextChar();

      //std::cout << "CHR: " << chr << " - status: " << iStatus << std::endl;

      switch ( iStatus ) {

         case STATUS_BEGIN:  // outside nodes
            switch ( chr ) {
               case MXML_LINE_TERMINATOR: nextLine() ; break;
               // We repeat line terminator here for portability
               case MXML_SOFT_LINE_TERMINATOR: break;
               case ' ': case '\t': break;
               case '<': iStatus = STATUS_FIRSTCHAR; break;
               default:  // it is a data node
                  m_type = typeData;
                  m_data = chr;
                  iStatus = STATUS_READ_DATA; // data
            }
         break;

         case STATUS_FIRSTCHAR: //inside a node, first character
            if ( chr == '/' ) {
               iStatus = STATUS_READ_TAG_NAME;
               m_type = typeFakeClosing;
            }
            else if ( chr == '!' ) {
               iStatus = STATUS_MAYBE_COMMENT;
            }
            else if ( chr == '?' ) {
               m_type = typePI;
               iStatus = STATUS_READ_TAG_NAME; // PI - read node name
            }
            else if ( isalpha( chr ) ) {
               m_type = typeTag;
               m_name = chr;
               iStatus = STATUS_READ_TAG_NAME2; // tag - read node name (2nd char)
            }
            else {
               throw MalformedError( Error::errInvalidNode, this );
            }
         break;

         case STATUS_MAYBE_COMMENT: //inside a possible comment (<!-/<!?)
            if ( chr == '-') {
               iStatus = STATUS_MAYBE_COMMENT2 ;
            }
            else if ( isalpha( chr ) ) {
               m_type = typeDirective;
               m_name = chr;
               iStatus = STATUS_READ_TAG_NAME; // read directive
            }
            else {
               throw MalformedError( Error::errInvalidNode, this );
            }
         break;

         case STATUS_MAYBE_COMMENT2:
            if ( chr == '-') {
               m_type = typeComment;
               iStatus = STATUS_READ_COMMENT; // read comment
            }
            else {
               throw MalformedError( Error::errInvalidNode, this );
            }
         break;

         case STATUS_READ_COMMENT:
            if ( chr == '-' ) {
               iStatus = STATUS_END_COMMENT1;
            }
            else {
               if ( chr == MXML_LINE_TERMINATOR )
                  nextLine();
               m_data += chr;
            }
         break;

         case STATUS_END_COMMENT1:
            if( chr == '-' )
               iStatus =  STATUS_END_COMMENT2;
            else {
               iStatus = STATUS_READ_COMMENT;
               m_data += "-" + chr;
            }
         break;

         case STATUS_END_COMMENT2:
            if ( chr == '>' ) {
               // comment is done!
               iStatus = STATUS_DONE;
            }
            else // any sequence of -- followed by any character != '>' is illegal
               throw MalformedError( Error::errCommentInvalid, this );
         break;

         // data:
         case STATUS_READ_DATA:
            if ( chr == '?' && ( m_type == typePI || m_type == typeXMLDecl ))
               iStatus = STATUS_READ_TAG_NAME3;
            else if ( chr == '>' && m_type != typeData ) {
               // done with this node (either PI or Directive)
               iStatus = STATUS_DONE;
            }
            else if ( chr == '<' && m_type == typeData ) {
               // done with data elements
               in.unget();
               iStatus = STATUS_DONE;
            }
            else {
               if ( m_type == typeData && chr == '&' &&
                     ! ( style & MXML_STYLE_NOESCAPE) )
               {
                  iStatus = STATUS_READ_ENTITY;
                  entity = "";
               }
               else{
                  if ( chr == MXML_LINE_TERMINATOR )
                     nextLine();
                  m_data += chr;
               }
            }
         break;

         // data + escape
         case STATUS_READ_ENTITY:
            if ( chr == ';' ) {
               // we see if we have a predef entity (also known as escape)
               if ( ( chr = parseEntity( entity ) ) != 0 )
                  m_data = chr;
               else
                  m_data = '&' + entity + ';';

               iStatus = STATUS_READ_DATA;
            }
            else if ( !isalnum( chr ) && chr != '_' && chr != '-' )
               //error - we have something like &amp &amp
               throw MalformedError( Error::errUnclosedEntity, this );
            else
               entity += chr;
         break;

         //Node name, first character
         case STATUS_READ_TAG_NAME:
            if ( isalpha( chr ) ) {
               m_name += chr;
               iStatus = STATUS_READ_TAG_NAME2; // second letter on
            }
            else
               throw MalformedError( Error::errInvalidNode, this );
         break;

         //Node name, from second character on
         case STATUS_READ_TAG_NAME2:
            if ( isalnum( chr ) || chr == '-'
                  || chr == '_' || chr == ':')
               m_name += chr;
            else if ( chr == '/' && m_type != typeFakeClosing )
               iStatus = STATUS_READ_TAG_NAME3; // waiting for '>' to close the tag
            else if ( chr == '?' && ( m_type == typePI || m_type == typeXMLDecl ))
               iStatus = STATUS_READ_TAG_NAME3;
            else if ( chr == '>') {
               if ( m_type == typeFakeClosing )
                  iStatus = STATUS_DONE;
               else
                  iStatus = STATUS_READ_SUBNODES;  // reading subnodes
            }
            else if ( chr == ' ' || chr == '\t' || chr == MXML_SOFT_LINE_TERMINATOR ||
                  chr == MXML_LINE_TERMINATOR )
            {
               if ( chr == MXML_LINE_TERMINATOR )
                  nextLine();
               // check for xml PI.
               if ( m_type == typePI && m_name == "xml" ) {
                  m_type = typeXMLDecl;
               }

               if ( m_type == typeTag || m_type == typeXMLDecl )
                  iStatus = STATUS_READ_ATTRIB; // read attributes
               else
                  iStatus = STATUS_READ_DATA; // read data.

            }
            else
               throw MalformedError( Error::errInvalidNode, this );
         break;

         // node name; waiting for '>'
         case STATUS_READ_TAG_NAME3:
            if ( chr != '>' )
               throw MalformedError( Error::errInvalidNode, this );
            // if not, we are done with this node
         return;

         // reading attributes
         case STATUS_READ_ATTRIB:
            if ( chr == '/' ||
                  ( chr == '?' && ( m_type == typePI || m_type == typeXMLDecl )))
            {
               iStatus = STATUS_READ_TAG_NAME3; // node name, waiting for '>'
            }
            else if ( chr == '>' )
               iStatus = STATUS_READ_SUBNODES; // subnodes
            else if ( chr == MXML_LINE_TERMINATOR || chr == ' ' || chr == '\t'
                        || chr == MXML_SOFT_LINE_TERMINATOR )
            {
               if ( chr == MXML_LINE_TERMINATOR )
                  nextLine();
            }
            else {
               in.unget();
               Attribute *attrib = new Attribute( in, style, line(), character() -1);
               m_attrib.push_back( attrib );
               setPosition( attrib->line(), attrib->character() );
            }
         break;

         case STATUS_READ_SUBNODES:
            in.unget();
            while ( in.good() ) {
               //std::cout << "Reading subnode" << std::endl;
               Node *child = new Node( in, style, line(), character() -1);
               setPosition( child->line(), child->character() );

               if ( child->m_type == typeData )
               {
                  if ( child->m_data == "" )  // delete empty data nodes
                     delete child;
                  else {
                     // set the-data-node for data promotion
                     if ( the_data_node == 0 )
                        the_data_node = child;
                     else
                        promote_data = false;
                     addBelow( child );
                  }
               }
               // have we found our closing node?
               else if ( child->m_type == typeFakeClosing ) {
                  //is the name valid?
                  if ( m_name == child->m_name ) {
                     iStatus = STATUS_DONE;
                     delete child;
                     break;
                  }
                  else { // We are unclosed!
                     delete child;
                     throw MalformedError( Error::errUnclosed, this );
                  }
               }
               else // in all the other cases, add subnodes.
                  addBelow( child );

            }
         break;
      } // switch
   } // while

   // now we do a little cleanup:
   // if we are a data or a comment node, trim the data
   // if we are a tag and we have just one data node, let's move it to our
   //   data member
   if ( m_type == typeData || m_type == typeComment )
   {
      int idx = m_data.find_first_not_of("\n\r \t");
      if( static_cast<unsigned int>(idx) != std::string::npos )
      {
         m_data = m_data.substr(idx);
         idx = m_data.find_last_not_of("\n\r \t");
         if( static_cast<unsigned int>(idx) != std::string::npos )
            m_data = m_data.substr( 0, idx+1 );
         else
            m_data = "";
      }
      else
         m_data = "";
   }

   if ( m_type == typeTag && promote_data && the_data_node != 0 )
   {
      m_data = the_data_node->m_data;
      // Data node have not children, and delete calls unlink()
      delete the_data_node;
   }

}
void QQuickStyledTextPrivate::parse()
{
    QList<QTextLayout::FormatRange> ranges;
    QStack<QTextCharFormat> formatStack;

    QString drawText;
    drawText.reserve(text.count());

    updateImagePositions = !imgTags->isEmpty();

    int textStart = 0;
    int textLength = 0;
    int rangeStart = 0;
    bool formatChanged = false;

    const QChar *ch = text.constData();
    while (!ch->isNull()) {
        if (*ch == lessThan) {
            if (textLength) {
                appendText(text, textStart, textLength, drawText);
            } else if (prependSpace) {
                drawText.append(space);
                prependSpace = false;
                hasSpace = true;
            }

            if (rangeStart != drawText.length() && formatStack.count()) {
                if (formatChanged) {
                    QTextLayout::FormatRange formatRange;
                    formatRange.format = formatStack.top();
                    formatRange.start = rangeStart;
                    formatRange.length = drawText.length() - rangeStart;
                    ranges.append(formatRange);
                    formatChanged = false;
                } else if (ranges.count()) {
                    ranges.last().length += drawText.length() - rangeStart;
                }
            }
            rangeStart = drawText.length();
            ++ch;
            if (*ch == slash) {
                ++ch;
                if (parseCloseTag(ch, text, drawText)) {
                    if (formatStack.count()) {
                        formatChanged = true;
                        formatStack.pop();
                    }
                }
            } else {
                QTextCharFormat format;
                if (formatStack.count())
                    format = formatStack.top();
                if (parseTag(ch, text, drawText, format)) {
                    formatChanged = true;
                    formatStack.push(format);
                }
            }
            textStart = ch - text.constData() + 1;
            textLength = 0;
        } else if (*ch == ampersand) {
            ++ch;
            appendText(text, textStart, textLength, drawText);
            parseEntity(ch, text, drawText);
            textStart = ch - text.constData() + 1;
            textLength = 0;
        } else if (ch->isSpace()) {
            if (textLength)
                appendText(text, textStart, textLength, drawText);
            if (!preFormat) {
                prependSpace = !hasSpace;
                for (const QChar *n = ch + 1; !n->isNull() && n->isSpace(); ++n)
                    ch = n;
                hasNewLine = false;
            } else  if (*ch == lineFeed) {
                drawText.append(QChar(QChar::LineSeparator));
                hasNewLine = true;
            } else {
                drawText.append(QChar(QChar::Nbsp));
                hasNewLine = false;
            }
            textStart = ch - text.constData() + 1;
            textLength = 0;
        } else {
            ++textLength;
        }
        if (!ch->isNull())
            ++ch;
    }
    if (textLength)
        appendText(text, textStart, textLength, drawText);
    if (rangeStart != drawText.length() && formatStack.count()) {
        if (formatChanged) {
            QTextLayout::FormatRange formatRange;
            formatRange.format = formatStack.top();
            formatRange.start = rangeStart;
            formatRange.length = drawText.length() - rangeStart;
            ranges.append(formatRange);
        } else if (ranges.count()) {
            ranges.last().length += drawText.length() - rangeStart;
        }
    }

    layout.setText(drawText);
    layout.setAdditionalFormats(ranges);
}