Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added pr-media/pr-11592-text-shape-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
199 changes: 189 additions & 10 deletions src/libslic3r/Emboss.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1475,9 +1475,16 @@ void Slic3r::Emboss::text2vshapes(EmbossShape & emboss_shape,
text_scales.emplace_back(real_scale);
}
cur_x = cursor.x() * standard_scale;
text_cursors.emplace_back(cur_x - last_x);
text_absolute_cursors.emplace_back(cur_x);
last_x = cur_x;
// After a line break cursor.x is reset; do not treat that as a negative advance into the previous line.
if (letter == '\n' || letter == '\r') {
text_cursors.emplace_back(0.f);
text_absolute_cursors.emplace_back(cur_x);
last_x = 0.f;
} else {
text_cursors.emplace_back(cur_x - last_x);
text_absolute_cursors.emplace_back(cur_x);
last_x = cur_x;
}
}
std::vector<Point> offset_xy;
if (bfc_fn) {// support_backup_fonts
Expand All @@ -1487,11 +1494,13 @@ void Slic3r::Emboss::text2vshapes(EmbossShape & emboss_shape,
}
std::vector<Vec2f> text_align_offsets;
text_align_offsets.resize(text_scales.size());
for (int i = 0; i < text_scales.size(); i++) {
float x = (float) offset_xy[i][0] * standard_scale;
float y = (float) offset_xy[i][1] * standard_scale;
text_align_offsets[i][0] = x;
text_align_offsets[i][1] = y;
for (int i = 0; i < (int) text_scales.size(); i++) {
if (i < (int) offset_xy.size()) {
text_align_offsets[i][0] = (float) offset_xy[i][0] * standard_scale;
text_align_offsets[i][1] = (float) offset_xy[i][1] * standard_scale;
} else {
text_align_offsets[i] = Vec2f::Zero();
}
}
emboss_shape.text_align_offsets = text_align_offsets;
std::vector<float> no_use_text_scales;
Expand Down Expand Up @@ -1693,6 +1702,172 @@ double Emboss::get_text_shape_scale(const FontProp &fp, const FontFile &ff)
return scale * SHAPE_SCALE;
}

namespace {
double measure_line_width_mm(FontFileWithCache &font_with_cache, const FontProp &font_prop, const std::wstring &line)
{
if (!font_with_cache.has_value() || line.empty())
return 0.;
const double scale = get_text_shape_scale(font_prop, *font_with_cache.font_file);
fontinfo_opt font_info_cache;
Point cursor(0, 0);
for (wchar_t letter : line) {
if (letter == '\n' || letter == '\r')
continue;
letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache);
}
return cursor.x() * scale;
}

double measure_text_height_mm(FontFileWithCache &font_with_cache, const FontProp &font_prop, const std::string &text)
{
if (!font_with_cache.has_value())
return 0.;
const unsigned lines = std::max(1u, get_count_lines(text));
const double scale = get_text_shape_scale(font_prop, *font_with_cache.font_file);
return get_line_height(*font_with_cache.font_file, font_prop) * scale * lines;
}
} // namespace

std::string Emboss::apply_text_wrap(FontFileWithCache &font_with_cache, const std::string &text, const FontProp &font_prop, double wrap_width_mm)
{
if (!font_with_cache.has_value() || wrap_width_mm <= 1e-3 || text.empty())
return text;

const std::wstring src = boost::nowide::widen(text);
std::wstring out;
out.reserve(src.size() + 8);

size_t para_start = 0;
while (para_start <= src.size()) {
size_t para_end = src.find('\n', para_start);
if (para_end == std::wstring::npos)
para_end = src.size();
const std::wstring paragraph = src.substr(para_start, para_end - para_start);

std::wstring line;
auto flush_line = [&]() {
if (line.empty())
return;
if (!out.empty())
out.push_back('\n');
out += line;
line.clear();
};

size_t i = 0;
while (i < paragraph.size()) {
size_t start = i;
if (paragraph[i] == ' ' || paragraph[i] == '\t') {
while (i < paragraph.size() && (paragraph[i] == ' ' || paragraph[i] == '\t'))
++i;
} else {
while (i < paragraph.size() && paragraph[i] != ' ' && paragraph[i] != '\t')
++i;
}
const std::wstring token = paragraph.substr(start, i - start);
const std::wstring trial = line + token;
if (line.empty() || measure_line_width_mm(font_with_cache, font_prop, trial) <= wrap_width_mm) {
line = trial;
continue;
}
flush_line();
if (measure_line_width_mm(font_with_cache, font_prop, token) <= wrap_width_mm) {
line = token;
} else {
for (wchar_t ch : token) {
std::wstring next = line;
next.push_back(ch);
if (!line.empty() && measure_line_width_mm(font_with_cache, font_prop, next) > wrap_width_mm) {
flush_line();
line.assign(1, ch);
} else {
line.swap(next);
}
}
}
}
flush_line();

// Preserve blank lines from consecutive Enter presses
if (para_end < src.size() && paragraph.empty()) {
if (!out.empty())
out.push_back('\n');
}

if (para_end >= src.size())
break;
para_start = para_end + 1;
}

return boost::nowide::narrow(out);
}

std::string Emboss::fit_text_to_box(FontFileWithCache &font_with_cache,
FontProp & font_prop,
const std::string &text,
double wrap_width_mm,
double wrap_height_mm,
bool do_wrap,
bool do_shrink)
{
if (!font_with_cache.has_value() || text.empty())
return text;

const float original_size = font_prop.size_in_mm;
std::string working = text;
// Work on a private glyph cache so fitting does not wipe the caller's style-manager cache.
FontFileWithCache local_font = font_with_cache;
local_font.cache = std::make_shared<Glyphs>();

auto rebuild = [&](float size) {
font_prop.size_in_mm = size;
local_font.cache = std::make_shared<Glyphs>();
working = do_wrap && wrap_width_mm > 1e-3 ? apply_text_wrap(local_font, text, font_prop, wrap_width_mm) : text;
};

rebuild(original_size);

if (!do_shrink || wrap_width_mm <= 1e-3)
return working;

auto fits = [&]() {
// Width: every line must fit (wrap already enforces when enabled; still check for shrink-only)
std::wstring ws = boost::nowide::widen(working);
size_t start = 0;
while (start <= ws.size()) {
size_t end = ws.find('\n', start);
if (end == std::wstring::npos)
end = ws.size();
if (measure_line_width_mm(local_font, font_prop, ws.substr(start, end - start)) > wrap_width_mm + 1e-3)
return false;
if (end >= ws.size())
break;
start = end + 1;
}
if (wrap_height_mm > 1e-3 && measure_text_height_mm(local_font, font_prop, working) > wrap_height_mm + 1e-3)
return false;
return true;
};

if (fits())
return working;

float lo = 1.f;
float hi = original_size;
for (int iter = 0; iter < 16; ++iter) {
float mid = 0.5f * (lo + hi);
rebuild(mid);
if (fits())
lo = mid;
else
hi = mid;
}
rebuild(lo);
if (font_prop.size_in_mm < 1.f)
font_prop.size_in_mm = 1.f;
return working;
}

namespace {

void add_quad(uint32_t i1,
Expand Down Expand Up @@ -2193,8 +2368,8 @@ void align_shape(ExPolygonsWithIds &shapes, std::vector<Point> &offset_xy, const

// Speed up for left aligned text
if (prop.align.first == FontProp::HorizontalAlign::left){
// already horizontaly aligned
offset_xy.emplace_back(Point(0, y_offset));
// already horizontaly aligned — still emit one offset per character for callers
offset_xy.assign(shapes.size(), Point(0, y_offset));
for (ExPolygonsWithId& shape : shapes)
for (ExPolygon &s : shape.expoly)
s.translate(Point(0, y_offset));
Expand All @@ -2216,9 +2391,11 @@ void align_shape(ExPolygonsWithIds &shapes, std::vector<Point> &offset_xy, const
Point offset(
get_align_x_offset(prop.align.first, shape_bb, get_line_bb(0)),
y_offset);
offset_xy.reserve(shapes.size());
for (size_t i = 0; i < shapes.size(); ++i) {
wchar_t letter = text[i];
if (letter == '\n'){
offset_xy.emplace_back(offset); // keep 1:1 with shapes/text for multiline
offset.x() = get_align_x_offset(prop.align.first, shape_bb, get_line_bb(i + 1));
continue;
}
Expand Down Expand Up @@ -2272,9 +2449,11 @@ void align_shape(ExPolygonsWithIds & shapes,

// Align x line by line
Point main_offset(get_align_x_offset(prop.align.first, shape_bb, get_line_bb(0)), main_y_offset);
offset_xy.reserve(shapes.size());
for (size_t i = 0; i < shapes.size(); ++i) {
wchar_t letter = text[i];
if (letter == '\n') {//Enter the next line of text
offset_xy.emplace_back(main_offset); // keep 1:1 with shapes/text for multiline
main_offset.x() = get_align_x_offset(prop.align.first, shape_bb, get_line_bb(i + 1));
continue;
}
Expand Down
19 changes: 19 additions & 0 deletions src/libslic3r/Emboss.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,25 @@ namespace Emboss
/// <returns>Conversion to mm</returns>
double get_text_shape_scale(const FontProp &fp, const FontFile &ff);

/// <summary>
/// Insert line breaks so each line fits wrap_width_mm. Preserves existing '\n'.
/// Word-wraps on spaces; breaks long words by character when needed.
/// </summary>
std::string apply_text_wrap(FontFileWithCache &font_with_cache, const std::string &text, const FontProp &font_prop, double wrap_width_mm);

/// <summary>
/// Optionally wrap text to width and/or shrink FontProp::size_in_mm so the layout fits a box.
/// When wrap_height_mm &lt;= 0, only width is enforced.
/// </summary>
/// <returns>Text used for shape generation (may include inserted '\n')</returns>
std::string fit_text_to_box(FontFileWithCache &font_with_cache,
FontProp & font_prop,
const std::string &text,
double wrap_width_mm,
double wrap_height_mm,
bool do_wrap,
bool do_shrink);

/// <summary>
/// getter of font info by collection defined in prop
/// </summary>
Expand Down
27 changes: 26 additions & 1 deletion src/libslic3r/Format/bbs_3mf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ static constexpr const char* THICKNESS_ATTR = "thickness";
static constexpr const char* EMBEDED_DEPTH_ATTR = "embeded_depth";
static constexpr const char* ROTATE_ANGLE_ATTR = "rotate_angle";
static constexpr const char* TEXT_GAP_ATTR = "text_gap";
static constexpr const char* WRAP_TEXT_ATTR = "wrap_text";
static constexpr const char* WRAP_WIDTH_ATTR = "wrap_width";
static constexpr const char* WRAP_HEIGHT_ATTR = "wrap_height";
static constexpr const char* AUTO_SHRINK_ATTR = "auto_shrink";
static constexpr const char* BOLD_ATTR = "bold";
static constexpr const char* ITALIC_ATTR = "italic";
static constexpr const char *SURFACE_TYPE = "surface_type";
Expand Down Expand Up @@ -4979,13 +4983,25 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
text_info.text_configuration.style.prop.boldness = bbs_get_attribute_value_float(attributes, num_attributes, BOLDNESS_ATTR);
text_info.text_configuration.style.prop.skew = bbs_get_attribute_value_float(attributes, num_attributes, SKEW_ATTR);
}
if (bbs_has_attribute_value_int(attributes, num_attributes, LINE_GAP_ATTR))
text_info.text_configuration.style.prop.line_gap = bbs_get_attribute_value_int(attributes, num_attributes, LINE_GAP_ATTR);
if (bbs_has_attribute_value_int(attributes, num_attributes, CHAR_GAP_ATTR))
text_info.text_configuration.style.prop.char_gap = bbs_get_attribute_value_int(attributes, num_attributes, CHAR_GAP_ATTR);
text_info.m_curr_font_idx = bbs_get_attribute_value_int(attributes, num_attributes, FONT_INDEX_ATTR);

text_info.m_font_size = bbs_get_attribute_value_float(attributes, num_attributes, FONT_SIZE_ATTR);
text_info.m_thickness = bbs_get_attribute_value_float(attributes, num_attributes, THICKNESS_ATTR);
text_info.m_embeded_depth = bbs_get_attribute_value_float(attributes, num_attributes, EMBEDED_DEPTH_ATTR);
text_info.m_rotate_angle = bbs_get_attribute_value_float(attributes, num_attributes, ROTATE_ANGLE_ATTR);
text_info.m_text_gap = bbs_get_attribute_value_float(attributes, num_attributes, TEXT_GAP_ATTR);
if (bbs_has_attribute_value_int(attributes, num_attributes, WRAP_TEXT_ATTR))
text_info.m_wrap_text = bbs_get_attribute_value_int(attributes, num_attributes, WRAP_TEXT_ATTR) != 0;
if (bbs_has_attribute_value_int(attributes, num_attributes, WRAP_WIDTH_ATTR))
text_info.m_wrap_width_mm = bbs_get_attribute_value_float(attributes, num_attributes, WRAP_WIDTH_ATTR);
if (bbs_has_attribute_value_int(attributes, num_attributes, WRAP_HEIGHT_ATTR))
text_info.m_wrap_height_mm = bbs_get_attribute_value_float(attributes, num_attributes, WRAP_HEIGHT_ATTR);
if (bbs_has_attribute_value_int(attributes, num_attributes, AUTO_SHRINK_ATTR))
text_info.m_auto_shrink = bbs_get_attribute_value_int(attributes, num_attributes, AUTO_SHRINK_ATTR) != 0;

text_info.m_bold = bbs_get_attribute_value_int(attributes, num_attributes, BOLD_ATTR);
text_info.m_italic = bbs_get_attribute_value_int(attributes, num_attributes, ITALIC_ATTR);
Expand Down Expand Up @@ -8015,12 +8031,17 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
void _add_text_info_to_archive(std::stringstream& stream, const TextInfo& text_info) {
stream << " <" << TEXT_INFO_TAG << " ";

stream << TEXT_ATTR << "=\"" << xml_escape(text_info.m_text) << "\" ";
// Preserve newlines/tabs in text attributes (XML attribute whitespace normalisation).
stream << TEXT_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(text_info.m_text) << "\" ";
stream << FONT_NAME_ATTR << "=\"" << xml_escape(text_info.m_font_name) << "\" ";
stream << FONT_VERSION_ATTR << "=\"" << text_info.m_font_version << "\" ";
stream << STYLE_NAME_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(text_info.text_configuration.style.name) << "\" ";
stream << BOLDNESS_ATTR << "=\"" << text_info.text_configuration.style.prop.boldness.value_or(0) << "\" ";
stream << SKEW_ATTR << "=\"" << text_info.text_configuration.style.prop.skew.value_or(0) << "\" ";
if (text_info.text_configuration.style.prop.line_gap.has_value())
stream << LINE_GAP_ATTR << "=\"" << *text_info.text_configuration.style.prop.line_gap << "\" ";
if (text_info.text_configuration.style.prop.char_gap.has_value())
stream << CHAR_GAP_ATTR << "=\"" << *text_info.text_configuration.style.prop.char_gap << "\" ";

stream << FONT_INDEX_ATTR << "=\"" << text_info.m_curr_font_idx << "\" ";

Expand All @@ -8029,6 +8050,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
stream << EMBEDED_DEPTH_ATTR << "=\"" << text_info.m_embeded_depth << "\" ";
stream << ROTATE_ANGLE_ATTR << "=\"" << text_info.m_rotate_angle << "\" ";
stream << TEXT_GAP_ATTR << "=\"" << text_info.m_text_gap << "\" ";
stream << WRAP_TEXT_ATTR << "=\"" << (text_info.m_wrap_text ? 1 : 0) << "\" ";
stream << WRAP_WIDTH_ATTR << "=\"" << text_info.m_wrap_width_mm << "\" ";
stream << WRAP_HEIGHT_ATTR << "=\"" << text_info.m_wrap_height_mm << "\" ";
stream << AUTO_SHRINK_ATTR << "=\"" << (text_info.m_auto_shrink ? 1 : 0) << "\" ";

stream << BOLD_ATTR << "=\"" << (text_info.m_bold ? 1 : 0) << "\" ";
stream << ITALIC_ATTR << "=\"" << (text_info.m_italic ? 1 : 0) << "\" ";
Expand Down
8 changes: 6 additions & 2 deletions src/libslic3r/Model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,10 @@ struct TextInfo
float m_embeded_depth = 0.f;
float m_rotate_angle = 0;
float m_text_gap = 0.f;
bool m_wrap_text = false;
float m_wrap_width_mm = 40.f;
float m_wrap_height_mm = 0.f; // 0 = no height limit
bool m_auto_shrink = false;
bool m_is_surface_text = false;//for old
bool m_keep_horizontal = false;//for old
enum TextType {
Expand All @@ -867,8 +871,8 @@ struct TextInfo

RaycastResult m_rr;
template<typename Archive> void serialize(Archive &ar) {
ar(m_font_name, m_font_version, m_font_size, m_curr_font_idx, m_bold, m_italic, m_thickness, m_embeded_depth, m_rotate_angle, m_text_gap, m_surface_type, m_text,
m_rr,text_configuration);
ar(m_font_name, m_font_version, m_font_size, m_curr_font_idx, m_bold, m_italic, m_thickness, m_embeded_depth, m_rotate_angle, m_text_gap, m_wrap_text,
m_wrap_width_mm, m_wrap_height_mm, m_auto_shrink, m_surface_type, m_text, m_rr, text_configuration);
}
TextConfiguration text_configuration;
};
Expand Down
Loading