void ParagraphLayout::_DrawSpan(BView* view, BPoint offset, const TextSpan& span, int32 textOffset) const { const BString& text = span.Text(); if (text.Length() == 0) return; const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset); const LineInfo& line = fLineInfos.ItemAtFast(glyph.lineIndex); offset.x += glyph.x; offset.y += line.y + line.maxAscent; const CharacterStyle& style = span.Style(); view->SetFont(&style.Font()); view->SetHighColor(style.ForegroundColor()); // TODO: Implement other style properties escapement_delta delta; delta.nonspace = line.extraGlyphSpacing; delta.space = line.extraWhiteSpacing; // TODO: Fix in app_server: First glyph should not be shifted by delta. if (text[0] == ' ') offset.x -= delta.space; else offset.x -= delta.nonspace; view->DrawString(span.Text(), offset, &delta); }
bool ParagraphLayout::_AppendGlyphInfos(const TextSpan& span) { int charCount = span.CountChars(); if (charCount == 0) return true; const BString& text = span.Text(); const BFont& font = span.Style().Font(); // Allocate arrays float* escapementArray = new (std::nothrow) float[charCount]; if (escapementArray == NULL) return false; ArrayDeleter<float> escapementDeleter(escapementArray); // Fetch glyph spacing information font.GetEscapements(text, charCount, escapementArray); // Append to glyph buffer and convert escapement scale float size = font.Size(); const char* c = text.String(); for (int i = 0; i < charCount; i++) { if (!_AppendGlyphInfo(UTF8ToCharCode(&c), escapementArray[i] * size, span.Style())) { return false; } } return true; }
bool Paragraph::Insert(int32 offset, const TextSpan& newSpan) { _InvalidateCachedLength(); int32 index = 0; while (index < fTextSpans.CountItems()) { const TextSpan& span = fTextSpans.ItemAtFast(index); if (offset - span.CountChars() < 0) break; offset -= span.CountChars(); index++; } if (fTextSpans.CountItems() == index) return Append(newSpan); // Try to merge with span at index if the TextStyles are equal TextSpan span = fTextSpans.ItemAtFast(index); if (span.Style() == newSpan.Style()) { span.Insert(offset, newSpan.Text()); return fTextSpans.Replace(index, span); } if (offset == 0) { if (index > 0) { // Try to merge with TextSpan before if offset == 0 && index > 0 TextSpan span = fTextSpans.ItemAtFast(index - 1); if (span.Style() == newSpan.Style()) { span.Insert(span.CountChars(), newSpan.Text()); return fTextSpans.Replace(index - 1, span); } } // Just insert the new span before the one at index return fTextSpans.Add(newSpan, index); } // Split the span, TextSpan spanBefore = span.SubSpan(0, offset); TextSpan spanAfter = span.SubSpan(offset, span.CountChars() - offset); return fTextSpans.Replace(index, spanBefore) && fTextSpans.Add(newSpan, index + 1) && fTextSpans.Add(spanAfter, index + 2); }
bool Paragraph::Append(const TextSpan& span) { // Try to merge with last span if the TextStyles are equal if (fTextSpans.CountItems() > 0) { const TextSpan& lastSpan = fTextSpans.LastItem(); if (lastSpan.Style() == span.Style()) { BString text(lastSpan.Text()); text.Append(span.Text()); fTextSpans.Remove(); return fTextSpans.Add(TextSpan(text, span.Style())); } } return fTextSpans.Add(span); }
bool Paragraph::Prepend(const TextSpan& span) { _InvalidateCachedLength(); // Try to merge with first span if the TextStyles are equal if (fTextSpans.CountItems() > 0) { const TextSpan& firstSpan = fTextSpans.ItemAtFast(0); if (firstSpan.Style() == span.Style()) { BString text(span.Text()); text.Append(firstSpan.Text()); return fTextSpans.Replace(0, TextSpan(text, span.Style())); } } return fTextSpans.Add(span, 0); }
void ParagraphLayout::_DrawSpan(BView* view, const TextSpan& span, int32 textOffset) const { const GlyphInfo& glyph = fGlyphInfos.ItemAtFast(textOffset); const LineInfo& line = fLineInfos.ItemAtFast(glyph.lineIndex); float x = glyph.x; float y = line.y + line.maxAscent; const CharacterStyle& style = span.Style(); view->SetFont(&style.Font()); view->SetHighColor(style.ForegroundColor()); // TODO: Implement other style properties // TODO: Implement correct glyph spacing view->DrawString(span.Text(), BPoint(x, y)); }
bool Paragraph::Remove(int32 offset, int32 length) { if (length == 0) return true; _InvalidateCachedLength(); int32 index = 0; while (index < fTextSpans.CountItems()) { const TextSpan& span = fTextSpans.ItemAtFast(index); if (offset - span.CountChars() < 0) break; offset -= span.CountChars(); index++; } if (index >= fTextSpans.CountItems()) return false; TextSpan span(fTextSpans.ItemAtFast(index)); int32 removeLength = std::min(span.CountChars() - offset, length); span.Remove(offset, removeLength); length -= removeLength; index += 1; // Remove more spans if necessary while (length > 0 && index < fTextSpans.CountItems()) { int32 spanLength = fTextSpans.ItemAtFast(index).CountChars(); if (spanLength <= length) { fTextSpans.Remove(index); length -= spanLength; } else { // Reached last span removeLength = std::min(length, spanLength); TextSpan lastSpan = fTextSpans.ItemAtFast(index).SubSpan( removeLength, spanLength - removeLength); // Try to merge with first span, otherwise replace span at index if (lastSpan.Style() == span.Style()) { span.Insert(span.CountChars(), lastSpan.Text()); fTextSpans.Remove(index); } else { fTextSpans.Replace(index, lastSpan); } break; } } // See if anything from the TextSpan at offset remained, keep it as empty // span if it is the last remaining span. index--; if (span.CountChars() > 0 || fTextSpans.CountItems() == 1) { fTextSpans.Replace(index, span); } else { fTextSpans.Remove(index); index--; } // See if spans can be merged after one has been removed. if (index >= 0 && index + 1 < fTextSpans.CountItems()) { const TextSpan& span1 = fTextSpans.ItemAtFast(index); const TextSpan& span2 = fTextSpans.ItemAtFast(index + 1); if (span1.Style() == span2.Style()) { span = span1; span.Append(span2.Text()); fTextSpans.Replace(index, span); fTextSpans.Remove(index + 1); } } return true; }