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; }
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 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)); }
void TextBuffer::insertSpan( std::string s, TextSpan &span ) { startEdit( span.begin, span.end ); if ( !span.empty() ) removeSpan(span); for ( t_CKUINT i=0; i < s.size(); i++) { if ( s[i] == '\n' ) insertLine( span.end ); else insertChar( s[i], span.end ); } endEdit ( span.begin, span.end ); }
void TextContent::paste(string s, TextSpan &span) { if ( !span.empty() ) _buf->removeSpan(span); _buf->insertSpan( s, span ); _buf->closeEdit(); }
bool Paragraph::Insert(int32 offset, const TextSpan& newSpan) { int32 index = 0; while (index < fTextSpans.CountItems()) { const TextSpan& span = fTextSpans.ItemAtFast(index); if (offset - span.CharCount() < 0) break; offset -= span.CharCount(); } 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.CharCount(), 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.CharCount() - offset); return fTextSpans.Replace(index, spanBefore) && fTextSpans.Add(newSpan, index + 1) && fTextSpans.Add(spanAfter, index + 2); }
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; }
void TextLayout::process(const FontSet &fontSet, const TextSpan &run) { auto buffer = hb_buffer_create(); map<uint32_t, Cluster> clusterMap; for (auto font : fontSet) { hb_buffer_clear_contents(buffer); run.apply(buffer); hb_shape(font->hbFont, buffer, NULL, 0); auto glyphCount = hb_buffer_get_length(buffer); auto glyphInfos = hb_buffer_get_glyph_infos(buffer, NULL); auto glyphPositions = hb_buffer_get_glyph_positions(buffer, NULL); bool hasMissingGlyphs = false; for (int i = 0; i < glyphCount; i++) { auto codepoint = glyphInfos[i].codepoint; auto cluster = glyphInfos[i].cluster; auto it = clusterMap.find(cluster); bool clusterFound = (it != clusterMap.end()); if (codepoint) { if (clusterFound && (it->second.font != font)) { continue; // CLUSTER FOUND, WITH ANOTHER FONT (E.G. SPACE) } else { auto offset = Vec2f(glyphPositions[i].x_offset, -glyphPositions[i].y_offset) * font->scale; float advance = glyphPositions[i].x_advance * font->scale.x; if (clusterFound) { it->second.addShape(codepoint, offset, advance); } else { clusterMap.insert(make_pair(cluster, Cluster(font, codepoint, offset, advance))); } } } else if (!clusterFound) { hasMissingGlyphs = true; } } if (!hasMissingGlyphs) { break; // NO NEED TO PROCEED TO THE NEXT FONT IN THE LIST } } if (run.direction == HB_DIRECTION_RTL) { for (auto it = clusterMap.rbegin(); it != clusterMap.rend(); ++it) { addCluster(it->second); } } else { for (auto it = clusterMap.begin(); it != clusterMap.end(); ++it) { addCluster(it->second); } } hb_buffer_destroy(buffer); }