Massively improve text parsing

pull/298/head
James H Ball 2025-04-04 14:59:17 +01:00
rodzic 6a1306ae15
commit ddd2163644
8 zmienionych plików z 267 dodań i 26 usunięć

Wyświetl plik

@ -781,7 +781,7 @@ void OscirenderAudioProcessor::setStateInformation(const void* data, int sizeInB
auto family = fontXml->getStringAttribute("family");
auto bold = fontXml->getBoolAttribute("bold");
auto italic = fontXml->getBoolAttribute("italic");
font = juce::Font(family, 1.0, (bold ? juce::Font::bold : 0) | (italic ? juce::Font::italic : 0));
font = juce::Font(family, FONT_SIZE, (bold ? juce::Font::bold : 0) | (italic ? juce::Font::italic : 0));
}
// close all files

Wyświetl plik

@ -178,7 +178,8 @@ public:
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(*this);
juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain);
const double FONT_SIZE = 1.0f;
juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), FONT_SIZE, juce::Font::plain);
ShapeSound::Ptr objectServerSound = new ShapeSound();

Wyświetl plik

@ -17,7 +17,7 @@ TxtComponent::TxtComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessor
auto updateFont = [this]() {
juce::SpinLock::ScopedLockType lock1(audioProcessor.parsersLock);
juce::SpinLock::ScopedLockType lock2(audioProcessor.effectsLock);
audioProcessor.font = juce::Font(installedFonts[font.getSelectedItemIndex()], 1.0, (bold.getToggleState() ? juce::Font::bold : 0) | (italic.getToggleState() ? juce::Font::italic : 0));
audioProcessor.font = juce::Font(installedFonts[font.getSelectedItemIndex()], audioProcessor.FONT_SIZE, (bold.getToggleState() ? juce::Font::bold : 0) | (italic.getToggleState() ? juce::Font::italic : 0));
};
font.onChange = updateFont;

Wyświetl plik

@ -1,7 +1,7 @@
#include "SvgParser.h"
#include "../shape/Line.h"
#include "../shape/QuadraticBezierCurve.h"
#include "../shape/CubicBezierCurve.h" // Add missing include
SvgParser::SvgParser(juce::String svgFile) {
auto doc = juce::XmlDocument::parse(svgFile);
@ -11,12 +11,11 @@ SvgParser::SvgParser(juce::String svgFile) {
if (composite != nullptr) {
auto contentArea = composite->getContentArea();
auto path = svg->getOutlineAsPath();
// apply transform to path to get the content area in the bounds -1 to 1
// Apply translation to center the path
path.applyTransform(juce::AffineTransform::translation(-contentArea.getX(), -contentArea.getY()));
path.applyTransform(juce::AffineTransform::scale(2 / contentArea.getWidth(), 2 / contentArea.getHeight()));
path.applyTransform(juce::AffineTransform::translation(-1, -1));
pathToShapes(path, shapes);
// Instead of separate scaling for width and height, just get the path as is
pathToShapes(path, shapes, true); // Enable normalization
Shape::removeOutOfBounds(shapes);
return;
}
@ -33,7 +32,7 @@ SvgParser::SvgParser(juce::String svgFile) {
SvgParser::~SvgParser() {}
void SvgParser::pathToShapes(juce::Path& path, std::vector<std::unique_ptr<Shape>>& shapes) {
void SvgParser::pathToShapes(juce::Path& path, std::vector<std::unique_ptr<Shape>>& shapes, bool normalise) {
juce::Path::Iterator pathIterator(path);
double x = 0;
double y = 0;
@ -72,6 +71,45 @@ void SvgParser::pathToShapes(juce::Path& path, std::vector<std::unique_ptr<Shape
break;
}
}
// Normalize shapes if requested and if there are shapes to normalize
if (normalise && !shapes.empty()) {
// Find the bounding box of all shapes
double minX = std::numeric_limits<double>::max();
double minY = std::numeric_limits<double>::max();
double maxX = std::numeric_limits<double>::lowest();
double maxY = std::numeric_limits<double>::lowest();
for (const auto& shape : shapes) {
auto start = shape->nextVector(0);
minX = std::min(minX, start.x);
minY = std::min(minY, start.y);
maxX = std::max(maxX, start.x);
maxY = std::max(maxY, start.y);
auto end = shape->nextVector(1);
minX = std::min(minX, end.x);
minY = std::min(minY, end.y);
maxX = std::max(maxX, end.x);
maxY = std::max(maxY, end.y);
}
// Calculate center of all shapes
double centerX = (minX + maxX) / 2.0;
double centerY = (minY + maxY) / 2.0;
// Calculate uniform scale factor based on the maximum dimension
double width = maxX - minX;
double height = maxY - minY;
double scaleFactor = 1.8 / std::max(width, height); // Scale to fit within -1 to 1
// Apply translation and scaling to all shapes
for (auto& shape : shapes) {
// First center, then scale
shape->translate(-centerX, -centerY, 0);
shape->scale(scaleFactor, scaleFactor, 1);
}
}
}
std::vector<std::unique_ptr<Shape>> SvgParser::draw() {

Wyświetl plik

@ -8,7 +8,7 @@ public:
SvgParser(juce::String svgFile);
~SvgParser();
static void pathToShapes(juce::Path& path, std::vector<std::unique_ptr<Shape>>& shapes);
static void pathToShapes(juce::Path& path, std::vector<std::unique_ptr<Shape>>& shapes, bool normalise = false);
std::vector<std::unique_ptr<Shape>> draw();
private:

Wyświetl plik

@ -12,13 +12,213 @@ TextParser::~TextParser() {
void TextParser::parse(juce::String text, juce::Font font) {
lastFont = font;
// Parse the text with formatting
attributedString = parseFormattedText(text, 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::GlyphArrangement glyphs;
glyphs.addFittedText(font, text, -2, -2, 4, 4, juce::Justification::centred, 2);
glyphs.createPath(textPath);
juce::String displayText = attributedString.getText();
// remove all whitespace
displayText = displayText.replaceCharacters(" \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) {
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);
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() {
@ -27,11 +227,11 @@ std::vector<std::unique_ptr<Shape>> TextParser::draw() {
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;
// clone with deep copy
std::vector<std::unique_ptr<Shape>> tempShapes;
for (auto& shape : shapes) {
tempShapes.push_back(shape->clone());
}
return tempShapes;
}

Wyświetl plik

@ -10,11 +10,15 @@ public:
~TextParser();
std::vector<std::unique_ptr<Shape>> draw();
private:
void parse(juce::String text, juce::Font font);
juce::AttributedString parseFormattedText(const juce::String& text, juce::Font font);
void processFormattedTextBody(const juce::String& text, juce::AttributedString& result, juce::Font font);
OscirenderAudioProcessor &audioProcessor;
std::vector<std::unique_ptr<Shape>> shapes;
juce::Font lastFont;
juce::String text;
juce::AttributedString attributedString;
};

Wyświetl plik

@ -447,8 +447,7 @@ void VisualiserComponent::setRecording(bool recording) {
if (codec == VideoCodec::H264) {
cmd += " -c:v libx264";
cmd += " -crf " + juce::String(recordingSettings.getCRF());
}
else if (codec == VideoCodec::H265) {
} else if (codec == VideoCodec::H265) {
cmd += " -c:v libx265";
cmd += " -crf " + juce::String(recordingSettings.getCRF());
#if JUCE_MAC && JUCE_ARM
@ -457,8 +456,7 @@ void VisualiserComponent::setRecording(bool recording) {
cmd += " -q:v " + juce::String(recordingSettings.getVideoToolboxQuality());
cmd += " -tag:v hvc1";
#endif
}
else if (codec == VideoCodec::VP9) {
} else if (codec == VideoCodec::VP9) {
cmd += " -c:v libvpx-vp9";
cmd += " -b:v 0";
cmd += " -crf " + juce::String(recordingSettings.getCRF());