kopia lustrzana https://github.com/jameshball/osci-render
Massively improve text parsing
rodzic
6a1306ae15
commit
ddd2163644
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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());
|
||||
|
|
Ładowanie…
Reference in New Issue