kopia lustrzana https://github.com/jameshball/osci-render
252 wiersze
9.0 KiB
C++
252 wiersze
9.0 KiB
C++
#include "TextParser.h"
|
|
#include "../svg/SvgParser.h"
|
|
#include "../PluginProcessor.h"
|
|
|
|
|
|
TextParser::TextParser(OscirenderAudioProcessor &p, juce::String text, juce::Font font) : audioProcessor(p), text(text) {
|
|
parse(text, font);
|
|
}
|
|
|
|
TextParser::~TextParser() {
|
|
}
|
|
|
|
void TextParser::parse(juce::String text, juce::Font font) {
|
|
lastFont = font;
|
|
|
|
// Apply formatting markers if the font is bold or italic
|
|
juce::String formattedText = text;
|
|
|
|
if (font.isBold()) {
|
|
formattedText = "*" + formattedText + "*";
|
|
}
|
|
|
|
if (font.isItalic()) {
|
|
formattedText = "_" + formattedText + "_";
|
|
}
|
|
|
|
// Parse the text with formatting
|
|
attributedString = parseFormattedText(formattedText, font);
|
|
|
|
// Create a TextLayout from the AttributedString
|
|
juce::TextLayout layout;
|
|
layout.createLayout(attributedString, 64.0f);
|
|
|
|
// Create a path from the TextLayout
|
|
juce::Path textPath;
|
|
|
|
juce::String displayText = attributedString.getText();
|
|
// remove all whitespace
|
|
displayText = displayText.removeCharacters(" \t\n\r");
|
|
int index = 0;
|
|
|
|
// Iterate through all lines and all runs in each line
|
|
for (int i = 0; i < layout.getNumLines(); ++i) {
|
|
const juce::TextLayout::Line& line = layout.getLine(i);
|
|
|
|
for (int j = 0; j < line.runs.size(); ++j) {
|
|
juce::TextLayout::Run* run = line.runs.getUnchecked(j);
|
|
|
|
// Create a GlyphArrangement for this run
|
|
juce::GlyphArrangement glyphs;
|
|
|
|
for (int k = 0; k < run->glyphs.size(); ++k) {
|
|
if (index >= displayText.length()) {
|
|
break;
|
|
}
|
|
juce::juce_wchar character = displayText[index];
|
|
juce::TextLayout::Glyph glyph = run->glyphs.getUnchecked(k);
|
|
juce::PositionedGlyph positionedGlyph = juce::PositionedGlyph(
|
|
run->font,
|
|
character,
|
|
glyph.glyphCode,
|
|
line.lineOrigin.x + glyph.anchor.x - 1,
|
|
line.lineOrigin.y + glyph.anchor.y - 1,
|
|
glyph.width,
|
|
juce::CharacterFunctions::isWhitespace(character)
|
|
);
|
|
glyphs.addGlyph(positionedGlyph);
|
|
index++;
|
|
}
|
|
|
|
// Add glyphs to the path
|
|
glyphs.createPath(textPath);
|
|
}
|
|
}
|
|
|
|
// If the layout has no text, fallback to original method
|
|
if (textPath.isEmpty()) {
|
|
juce::GlyphArrangement glyphs;
|
|
glyphs.addFittedText(font, text, -2, -2, 4, 4, juce::Justification::centred, 2);
|
|
glyphs.createPath(textPath);
|
|
}
|
|
|
|
// Convert path to shapes
|
|
shapes = std::vector<std::unique_ptr<Shape>>();
|
|
SvgParser::pathToShapes(textPath, shapes, true);
|
|
}
|
|
|
|
juce::AttributedString TextParser::parseFormattedText(const juce::String& text, juce::Font font) {
|
|
juce::AttributedString result;
|
|
|
|
// Handle headings first - they're line-based
|
|
juce::StringArray lines;
|
|
lines.addLines(text);
|
|
|
|
// Combine lines after handling headings for continuous formatting
|
|
juce::String processedText;
|
|
|
|
for (auto line : lines) {
|
|
// Handle headings
|
|
if (line.startsWith("##### ")) {
|
|
if (!processedText.isEmpty()) {
|
|
// Process any accumulated text before the heading
|
|
processFormattedTextBody(processedText, result, font);
|
|
processedText = juce::String();
|
|
}
|
|
juce::Font headingFont = font.boldened().withHeight(font.getHeight() * 1.1f);
|
|
result.append(parseFormattedText(line.substring(6), headingFont));
|
|
result.append("\n", font);
|
|
} else if (line.startsWith("#### ")) {
|
|
if (!processedText.isEmpty()) {
|
|
processFormattedTextBody(processedText, result, font);
|
|
processedText = juce::String();
|
|
}
|
|
juce::Font headingFont = font.boldened().withHeight(font.getHeight() * 1.25f);
|
|
result.append(parseFormattedText(line.substring(5), headingFont));
|
|
result.append("\n", font);
|
|
} else if (line.startsWith("### ")) {
|
|
if (!processedText.isEmpty()) {
|
|
processFormattedTextBody(processedText, result, font);
|
|
processedText = juce::String();
|
|
}
|
|
juce::Font headingFont = font.boldened().withHeight(font.getHeight() * 1.42f);
|
|
result.append(parseFormattedText(line.substring(4), headingFont));
|
|
result.append("\n", font);
|
|
} else if (line.startsWith("## ")) {
|
|
if (!processedText.isEmpty()) {
|
|
processFormattedTextBody(processedText, result, font);
|
|
processedText = juce::String();
|
|
}
|
|
juce::Font headingFont = font.boldened().withHeight(font.getHeight() * 1.7f);
|
|
result.append(parseFormattedText(line.substring(3), headingFont));
|
|
result.append("\n", font);
|
|
} else if (line.startsWith("# ")) {
|
|
if (!processedText.isEmpty()) {
|
|
processFormattedTextBody(processedText, result, font);
|
|
processedText = juce::String();
|
|
}
|
|
juce::Font headingFont = font.boldened().withHeight(font.getHeight() * 2.1f);
|
|
result.append(parseFormattedText(line.substring(2), headingFont));
|
|
result.append("\n", font);
|
|
} else {
|
|
// For regular text, add to the accumulated text with a newline
|
|
if (!processedText.isEmpty()) {
|
|
processedText += "\n";
|
|
}
|
|
processedText += line;
|
|
}
|
|
}
|
|
|
|
// Process any remaining text
|
|
if (!processedText.isEmpty()) {
|
|
processFormattedTextBody(processedText, result, font);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Helper function to process the body text with continuous formatting
|
|
void TextParser::processFormattedTextBody(const juce::String& text, juce::AttributedString& result, juce::Font font) {
|
|
juce::String currentText;
|
|
int i = 0;
|
|
|
|
// Track formatting positions
|
|
juce::Array<int> boldPositions;
|
|
juce::Array<int> italicPositions;
|
|
|
|
// First, find all formatting markers and record their positions
|
|
while (i < text.length()) {
|
|
if (text[i] == '*') {
|
|
boldPositions.add(i);
|
|
} else if (text[i] == '_') {
|
|
italicPositions.add(i);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
// Process the text with the recorded formatting positions
|
|
i = 0;
|
|
while (i < text.length()) {
|
|
// Check if we're at a bold marker
|
|
if (text[i] == '*' && boldPositions.indexOf(i) >= 0) {
|
|
int boldIndex = boldPositions.indexOf(i);
|
|
// If there's a matching pair
|
|
if (boldIndex % 2 == 0 && boldIndex + 1 < boldPositions.size()) {
|
|
// First append accumulated text with normal font
|
|
result.append(currentText, font);
|
|
currentText = juce::String();
|
|
|
|
// Extract and append the bold text
|
|
int endPos = boldPositions[boldIndex + 1];
|
|
juce::String boldText = text.substring(i + 1, endPos);
|
|
result.append(boldText, font.boldened());
|
|
|
|
// Move past the closing asterisk
|
|
i = endPos + 1;
|
|
} else {
|
|
// No matching pair, treat as literal
|
|
currentText += '*';
|
|
i++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Check if we're at an italic marker
|
|
if (text[i] == '_' && italicPositions.indexOf(i) >= 0) {
|
|
int italicIndex = italicPositions.indexOf(i);
|
|
// If there's a matching pair
|
|
if (italicIndex % 2 == 0 && italicIndex + 1 < italicPositions.size()) {
|
|
// First append accumulated text with normal font
|
|
result.append(currentText, font);
|
|
currentText = juce::String();
|
|
|
|
// Extract and append the italic text
|
|
int endPos = italicPositions[italicIndex + 1];
|
|
juce::String italicText = text.substring(i + 1, endPos);
|
|
result.append(italicText, font.italicised());
|
|
|
|
// Move past the closing underscore
|
|
i = endPos + 1;
|
|
} else {
|
|
// No matching pair, treat as literal
|
|
currentText += '_';
|
|
i++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Add current character to text buffer
|
|
currentText += text[i++];
|
|
}
|
|
|
|
// Add any remaining text
|
|
if (currentText.isNotEmpty()) {
|
|
result.append(currentText, font);
|
|
}
|
|
}
|
|
|
|
std::vector<std::unique_ptr<Shape>> TextParser::draw() {
|
|
// reparse text if font changes
|
|
if (audioProcessor.font != lastFont) {
|
|
parse(text, audioProcessor.font);
|
|
}
|
|
|
|
// clone with deep copy
|
|
std::vector<std::unique_ptr<Shape>> tempShapes;
|
|
|
|
for (auto& shape : shapes) {
|
|
tempShapes.push_back(shape->clone());
|
|
}
|
|
return tempShapes;
|
|
}
|