Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added text wrapping, consolidated text operations into new file #1542

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

amatulic
Copy link
Contributor

  • Added text wrapping:
    • wraptext() function
    • array_text() module
    • textarray_boundingbox() function
  • Created new file text.scad for text wrapping, and moved text-related features into it:
    • text() from shapes2d.scad
    • text3d() and path_text() from shapes3d.scad

Re-tested examples, did some minor grammar fixing in comments.

@revarbat
Copy link
Collaborator

I'm not sure if there is anything that needs editing in .openscad_docsgenrc for adding this new file, but I recommend checking it out.

@adrianVmariano
Copy link
Collaborator

Typo "paragraph" in docs.

Should indent function bodies.

For consistency probably "text_array" instead of "textarray". (Note: before BOSL2 I never used underscores; that was Revar's requirement.)

The text_array_boundingbox function doesn't return a bounding box, only a width and height. Maybe it should be renamed text_array_size?

The array_text() module maybe should be rolled into text()? That is, if you pass a string it calls the current code and if you pass an array of strings or array of array of strings (?) it calls the new code.

Also, this module doesn't have any attachable handling, which is bad. You should always call attachable() unless there's some very compelling reason you can't, in which case you need to manually support a bunch of stuff---so many things actually that half of them are missing from text() because I didn't realize I needed to add them there. But in particular, take a look at text(). Do you see all the stuff in there that sets $ variables and so on. If you don't call attachable you need to do all that stuff...and more. (Take a look at attachable and what it does when it creates the main child object and then the rest of the children. All that should be happening in text....and in array_text().)

If array_text stays as a separate module I'm not sure about the name. Maybe it should be text_array() if it stays separate? But also, it should be grouped with text() as a rendering module in the docs. Maybe this is obvious, but we always need to think about laying our our code in the way that makes the most sense for a user reading the docs, NOT the way that makes the most sense for a programming reading the code!

@adrianVmariano
Copy link
Collaborator

Hmmm. Relatively new no-arg version of attachable() may eliminate need to avoid attachable(), though some manual setting may still be necessary for $ variables.

There's at least one line ending with ':' where the ':' could go to the next line.

@adrianVmariano
Copy link
Collaborator

It looks like using attachable() with no args can indeed replace the stuff currently in text() and text3d() and provides added functionality.

It looks to me like you have basically reimplemented the anchoring transform for a box instead of just invoking attachable() in array_text(), so it seems like you ought to be able to just invoke attachable with a size arg.

@adrianVmariano
Copy link
Collaborator

The main reason, I think, why text() and text3d() don't do it that way is that they are designed to work in the stable OpenSCAD where we do not know the dimensions of the text.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 12, 2025

I note that the text() implementation can't have things attached to it, in fact the docs for text() state "You cannot attach children to text." All that's needed is to for it to be attachable with an anchor. That's what I was going for too. I tested it, and there's one example using the anchor.

Merging array_text() into text() might save me the trouble of having to figure out attachments again. There may be a complication in that with multi-line text, halign aligns the multi-line text within its bounding box, and that bounding box is what gets the anchor. Another complication is the difference in default fonts, particularly between 2021.01 and the snapshot builds. For those reasons it may be best to keep them separate.

How is a bounding box defined if not with width and height? Those are the dimensions of the box. The position of the box depends on the anchor. I could call it something besides bounding box, maybe textarray_dimensions?

I tend never to use underscores in variable names, just in module and function names. Not a problem to change, however.

@adrianVmariano
Copy link
Collaborator

The reason you cannot attach children to text is that we don't know where the text is because we don't know it's size, so it is simply impossible to implement attachment. This is not true for text_array because you are assuming you have its dimensions, so what reason is there to not support attachment by just using attachable, which makes your code much simpler....and even more so when you consider what you'd need to add to make everything work without it.

All the other attachable features need to work, so you need to make sure that tagging, highlighting, colors and attachment and so on all work. As I said, text() and text3d() are actually broken right now because highlighting and ghosting don't work.

A bounding box is a size and position. As you say, this depends on the box position, hence it doesn't make sense to say you're producing a bounding box. Native OpenSCAD and hence also BOSL2 use "size" to refer to the dimensions of rectangles and cubes so the name should be text_array_size(). As I said, I don't use underscores at all...except in BOSL2 where we use them in all user-facing identifiers to separate all words.

Merging array_text() into text() will not save you any effort because you're going to have to do it as a simple two-way conditional, where you invoke the old code if the old OpenSCAD is running and you have a single string, otherwise you invoke the new code. Keep in mind that attachable does a ton of stuff. If you want attach() to work right you need to make sure you have set all the right $ variables, for example, and set a geometry type. Getting all of that right is much harder than just invoking attachable(), and in this case no weird anchor overrides are required, so it's straight forward. The poor man's quasi-anchoring currently available in text() and text3d() is not something to aspire to. It's a workaround hack.

This does raise a question, though, about anchoring consistency across versions. You can ban children like we do right now, but that seems a bit unfortunate. One would actually like to be able to do things like text("foo") attach(FWD) text("bar") attach(FWD) text("baz"); to create 3 lines of text, though admittedly this isn't necessary with multi-line-text. I suppose being able to position text between two pieces of geometry would be an example. But having children be sometimes supported and sometimes not supported seems kind of wonky. I guess there could be an error that says "Children are not supported in OpenSCAD version 2021.01"

@amatulic
Copy link
Contributor Author

amatulic commented Jan 12, 2025

The only way to get the bounding box is to use textmetrics, which doesn't exist in 2021.01. Perhaps text() could be modified to use this and then the full attachment capabilities would be possible.

Because the bounding box (which I shall rename to text_array_size) is a rectangle, and that's all we're concerned about for the purpose of attachments, how about for now I make the attachments the same as with square()?

The more I think about it, the more I think array_text() should be separate from text(). There are minor differences that could lead to confusion if mashed together into the same module:

  • array_text() needs an additional line height parameter
  • by necessity it uses different defaults for the font face if not specified by the user
  • the valign parameter is meaningless with wrapped text; it's always aligned to fit inside the bounding box, and that is what gets positioned with anchoring
  • the halign parameter doesn't align the text with respect to the origin like with text(), it aligns the text inside the bounding box; e.g. halign="right" right-justifies the text, and halign="center" centers the wrapped text within its container. I thought real hard about this, and decided this is what users would expect halign to do for multi-line text.
  • I was thinking (for later) to expand halign to include a value "justified" to justify both the left and right edges of the wrapped text, further counfounding the difference in meaning from this parameter in text(). That would be a complicated change, however, because I'd need to introduce a variable-width spacing that isn't part of the font. Right now it uses only information in fontmetrics.

P.S. Instead of repurposing halign I could could instead omit it as I do with valign and instead call the parameter justify. Hmm, yes, that would make sense. It could have the values left, right, center, and full.

@adrianVmariano
Copy link
Collaborator

I think you're right that we should not wrap the new functionality into text(). I was losing sight of the fact that text() is a native openscad command.

Your command should be a complete replacement for text and I'm not sure what it should be called, but I feel like it shouldn't have "array" in the name. It's just the new better command for doing text, like we have rect as an alternative to square and cuboid as an alternative to cube.

There should probably also be a 3d version like text3d.

I don't understand what the line_spacing parameter does. There should be a correct line spacing, which should be the default.

The "spacing" parameter is also probably something questionable that perhaps isn't needed. Adding space between letters in a font is typographically bad generally speaking. The font was designed with the correct spacing. But the spacing parameter adds a multiplier which creates a ridiculous look. There's no reason you'd ever want to use it. If you actually wanted to screw up the font by spacing letters apart I think you'd want to add a fixed amount between each letter, not a proportional amount.

It might also be appropriate for a BOSL2 font replacement module to fix the 0.72 factor bug in the font sizes. I'm not sure about that, though.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 13, 2025

The latest commit has some echo statements for debugging.

It works fine for snapshot OpenSCAD, but for 2021.01 it has weird behavior with parameters changing values.

Try this test in a snapshot and in 2021.01.

include<BOSL2/std.scad>
string = "Go placidly amid the noise and haste, \
and remember what peace there may be in silence.";
fontname = "Lucida Serif:style=Bold Italic";
text_array = textwrap(string, width=130, font=fontname);
color("lightblue") linear_extrude(4) write(text_array, font=fontname);

The width value is displayed just before passing it into _justify_text(), and then displayed again as the first line in that module. The value is unchanged as expected in snapshots, but changes to an array (output of _glyphdata() for some reason) in 2021.01.

@amatulic
Copy link
Contributor Author

I don't understand what the line_spacing parameter does. There should be a correct line spacing, which should be the default.

The default is line_spacing=1. It's the vertical couterpart to spacing in text(). Both are proportions of the spacing. At line_spacing=1 you get text lines spaced vertically in increments of the font's nominal line height.

I frequently make use of the spacing parameter in my work, because for most fonts that I need to print, at small sizes the spacing gets smaller than a line width on the 3D printer, causing engraved text to run together when printed. I nearly always set spacing=1.1 in most of my projects that need 3D printed text using size=8 or less.

@Jasonkoolman
Copy link

Jasonkoolman commented Jan 21, 2025

I frequently make use of the spacing parameter in my work, because for most fonts that I need to print, at small sizes the spacing gets smaller than a line width on the 3D printer, causing engraved text to run together when printed. I nearly always set spacing=1.1 in most of my projects that need 3D printed text using size=8 or less.

Line spacing (also known as line height) is indeed commonly used and quite important for having the ability to control it effectively.

Adding space between letters in a font is typographically bad generally speaking.

As an application and web developer with a UX background, I would disagree. There are valid use cases - for example, increasing letter spacing for buttons or headlines to enhance visibility and clarity.

@Jasonkoolman
Copy link

@amatulic For what it's worth, I’d love to see a version of justify_text made public. I often find myself using a basic implementation to center text such as:

/**
 * Centers child text based on its metrics.
 *
 * @param metrics   A textmetrics object.
 */
module center_text(metrics) {
    assert(
        !is_undef(metrics) && is_list(metrics.position) && is_list(metrics.size),
        "Invalid textmetrics object supplied."
    );

    cx = -(metrics.position.x + metrics.size.x / 2);
    cy = -(metrics.position.y + metrics.size.y / 2);

    translate([cx, cy])
        children();
}

@amatulic
Copy link
Contributor Author

@Jasonkoolman - everything is already finished with respect to text justification, centering, line wrapping etc. The problem now is that we're considering making all of this into a wholesale replacement for BOSL2's existing text(), which in turn is a replacement for OpenSCAD's text(). We're planning to call it write() to include all the additional features such as justification, anchoring, attachments, and wordwrapping.

However, when I made this PR I never intended to replace text(), my main focus was to implement wordwrapping as a separate thing, but it turned out that what I created has enough overlap that it can replace text() -- and that's going to be a big job. I've been putting it off because I know I'm in for a lot more revisions, especially regarding anchoring and attachments, which I understand as a user but not as a programmer. In the meantime I've been working on implementing isosurfaces and meta-balls, and that's nearly done.

@Jasonkoolman
Copy link

Jasonkoolman commented Jan 22, 2025

Thanks for the clarification! I’m not sure I fully understand the complexity of anchoring. In my mind, I’m imagining an extruded 2D shape with a rectangular bounding box. Are you thinking about adding anchors to the individual ‘faces’ of characters/glyphs, something of that nature?

@amatulic
Copy link
Contributor Author

@Jasonkoolman - the anchors and attachment points would be at the center, corners, and edges of the text bounding box, as well as the left, center, and right ends of the text baseline.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 24, 2025

I took a break from this mainly because I'm having trouble reconciling what we need to do. In the meantime I finished a PR for isosurfaces and metaballs.

Questions for @adrianVmariano

  1. With the ability to word wrap, there are eight text directions possible:
  • ltr+ttb (default western)
  • ltr+btt (none I could find)
  • rtl+ttb (Hebrew/Arabic)
  • rtl+btt (none I could find)
  • ttb+rtl (Chinese/Japanese)
  • ttb+ltr (Mongolian/Uyghur)
  • btt+ltr (Hanuno'o - minor Philippine language)
  • btt+rtl (Ancient Berber - seems to be the only one using this direction)
  • alternating rtl/ltr back and forth on each line (some extinct languages)
    Some of the languages are obscure. Given that write() will have a parameter set completely independent of text(), what do you suggest for parameters? maybe direction_primary and direction_secondary or just direction=ste+ste where 'ste' indicates 'start-to-end' (left, right, bottom, top)?
  1. Do you know what the language parameter in text() is for, or what it does? Is this always a two-letter code defined by ISO 639-1 (for which there are 183 codes)?
  2. Should word-wrapping be supported for vertical text?
  3. For anchoring, I envision the standard anchors like TOP+LEFT and CENTER would work on the text bounding box, but relative to anchor_valign which would be "top", "middle", "baseline", and "bottom" relative to the character glyph rectangle. This would apply only to anchors with a TOP or BOTTOM component. That is, anchor=BOTTOM+LEFT combined with anchor_valign="baseline" would anchor the bottom left of the bounding box at the baseline position of the last line of wrapped text. anchor=TOP combined with valign_anchor="bottom" would anchor the top center of the bounding box in at the bottom glyph position of the top line of wrapped text. What do you think?
  4. If the width parameter is missing, width should be considered infinite, wrapping wouldn't occur unless the text has newlines in it, and the bounding box would be determined by the size of the text rather than the supplied width.
  5. I suggest we do away with the notion of OpenSCAD's size meaning and simply use our own, like char_height. When someone thinks of a font size in terms of character height, I expect that's normally thought of as "height of an uppercase character above the baseline". This is actually pretty close to OpenSCAD's size; that is, for Liberation Mono, size=10 corresponds to about 11.6 maximum. I see no reason to maintain compatibility with OpenSCAD's text(), we should do what makes sense.

Something to think about for 3D text: in this golf ball stencil I designed, the text isn't just extruded, each character is extruded with scaling so that each character glyph occupies a solid angle of the sphere rather than a rectangular prism, resulting in elements of each glyph being mostly perpendicular to the sphere surface. New modules like text_cylinder() and text_sphere() might be useful with this sort of scaling. But for now I want to focus on the 2D problem.

@adrianVmariano
Copy link
Collaborator

  1. My immediate thoughts are that you can focus on horizontal text. A brief investigation for vertical text:

https://stackoverflow.com/questions/2090302/word-wrap-algorithms-for-japanese

indicates that it may have wildly different wrapping conventions, and since we don't understand all those things, it seems like it should wait for a user request from someone who does. Arabic and Hebrew both have words formed from groups of letters separated by spaces that get wrapped the normal way, so supporting right to left seems reasonable.

It belatedly occurs to me that people write English in ttb direction sometimes. I'm inclined to say that word wrapping is not important for this case---people don't write paragraphs that way, usually just a single line. So...that again leaves just horizontal text.

I read your question again, though and maybe you're asking for how the UI specifies direction? If that's what you want, I think that direction_primary and direction_secondary is too verbose. I'd favor your other proposal, which I assume was meant to be a string option. There is the question about whether abbreviated defaults are permitted, though, like "ttb" is the default if you only give "ltr" or "rtl". I think that would make sense. This is probably worth bringing up on the chat to see if anyone has a better idea.

  1. I don't know what either "language" or "script" do. I asked Jordan and he said: They feed into Harfbuzz, which does kerning, ligatures, and other layout stuff. I don’t know what they are used for.

  2. As noted above, I say skip it until it's requested by someone who understands how it should work. If it should work as suggested above it's a pretty different process and maybe a separate mode is needed.

  3. Regarding anchoring, we want to avoid making modal anchoring if we can, because this imposes limitations that you can only use the anchors from one mode at a time. Keep in mind that anchors have two completely different uses: the positioning of the object itself into the model but also the relative location of child objects. You may want one kind of anchor for the former and a different for the latter on the same object at the same time. Consider that I want to create a text and use its center anchor to position it somewhere and then I want a second piece of text aligned with the first but spaced apart, so I need the baseline anchors for that. This is why I keep saying that we want named anchors for the baseline anchors so all anchors are simultaneously available.. So here's an idea maybe better fleshed out: we have the usual anchors operate on the bounding box of the text. Then we have named anchors for every line that are located on the baseline, and numbered starting at line 0. And then an un-numbered anchor that gives the first baseline (the same as the 0 labeled one). I'm thinking names for these anchors could be base0, base1, base2, etc for the centered anchors, and then base_left# and base_right#, but things do get a bit messy with a 3d version, where we need base_top_right#, base_bot_right#, base_bot#, base_top#, etc, etc. Maybe also with seeing if anybody has any thoughts on the chat.

5 and 6. I asked Jordan what he thought about specifying size and he suggested a case I hadn't thought of, but which I think we should support, which is creating a text that fits into a specified box. That is, you scale the font so the text fits. So it could be that you give no font size but you give a box size and then the text is made to fit into the box. I'm not sure how you'd decide on the number of lines. This whole category seems like it needs additional consideration. You could just specify the actual height. You could specify just the actual width. You could specify the height of the nominal ascender that is given by font metrics, though we might want to check if that seems to match up with anything. I recall vaguely some confusion in the past about what those values actually meant. But maybe it was the "max" values that were confusing.

One thing I think we absolutely should provide is a way to specify fonts using the conventional font size, which means giving the font size as the OpenSCAD size multiplied by 0.72. This is how fonts are specified in every other system in the world and is the universal standard for describing font sizing.

Another thing to be alert to is that if we provide a measure based on actual text size then two piece of text with the "same" size won't actually be the same size, and so they won't mate together. So it's important to have a clear distinction between setting font size and setting text size.

Jordan also said this: Spacing around text is tricky. I just wrote a general name tag program (eg for luggage tags) and found, for instance, that I wanted to adjust spacing based on whether the text had descenders - and if it did, I wanted extra spacing above the text for symmetry.

  1. Regarding spacing between letters, I tried to look into this a little more. I found the quote: "A man who would letterspace lower case would steal sheep" basically indicating that generally, adding space between letters is bad. I'm afraid to say that citing web page design as a source of font expertise isn't very compelling---I've seen web pages. I did find the claim that two valid use cases exist for adding space between letters, which is called "tracking". One is for writing text in all caps. The other one appears to relate to extreme font sizes, where people suggest increasing letter spacing for small fonts and decreasing it for large fonts. This sounds to me like a case of abusing the font, because if the font was designed to be used at that size it would be properly spaced there and you wouldn't be trying to muck with the letter spacing in lower case.

However, Jordan noted that OpensCAD requests all fonts at a fixed size. (He said 1000 units.) This means that potentially fonts in OpenSCAD will not be well formed and we might possibly be abusing the fonts systematically in OpenSCAD when using them at smaller sizes. This suggests that there might possibly be an application for letter spacing in these fonts. But the way OpenSCAD does it is clearly wrong, where it inserts space after a letter that is proportional to the letter:

image

If we're going to support letterspacing or tracking I think the right way to do it is to insert a fixed constant space between every pair of letters. That space should probably be specified in units of em so it's tied to the font size. This would need to be done manually by the code, where each letter is positioned and the extra space added. And then there's the problem of ligatures. If you break the font up into letters you'll break the ligatures, since you don't know where they are. This was one issue I had with path_text where there was really no way to preserve ligatures.

@amatulic
Copy link
Contributor Author

One thing I think we absolutely should provide is a way to specify fonts using the conventional font size, which means giving the font size as the OpenSCAD size multiplied by 0.72. This is how fonts are specified in every other system in the world and is the universal standard for describing font sizing.

To me this makes no sense at all, sorry. When I look at the font spec data, I see nothing in there corresponding to that ratio. The ascender is typically less in proportion to the total font height. When I google for 72% in the context of fonts, I find nothing to support the notion that this practice even exists, let alone being a worldwide standard. I feel we should do away with it altogether. To most people it's obscure, if it exists at all. If they request a font size, they expect a character glyph to be that height.

@Jasonkoolman
Copy link

Jasonkoolman commented Jan 25, 2025

@adrianVmariano Regarding letter spacing: At the Chamber of Commerce, we leverage letter spacing to enhance readability in specific use cases, a practice supported by A/B testing and user research. For instance, wider letter spacing has proven effective in improving comprehension and usability.

I’d also like to reference Figma's definition of letter spacing, which states:

  1. Accessibility considerations
    Letter spacing plays a crucial role in accessibility. Research suggests that wider spacing can improve reading comprehension and speed, particularly for individuals with dyslexia. Adhering to guidelines set by organizations like the World Wide Web Consortium (W3C), which recommend letter spacing of at least 0.12 times the font size, helps ensure digital content is accessible to a broader range of users.

That said, I agree that most fonts are already configured with well-optimized spacing by their designers, and manually adjusting tracking can sometimes feel unnecessary or overly complex. In many cases, it’s best to trust the typeface and use it as intended unless there’s a specific need.

I enjoyed the quote you shared - it’s a fun take, but I’m not sure how realistic it is in today’s design landscape. No offence, just sharing my honest opinion. Keep up the great work, and thanks for this beautiful library!

@adrianVmariano
Copy link
Collaborator

adrianVmariano commented Jan 26, 2025

@Jasonkoolman I open to the possibility that I'm missing something here (and generally speaking). The sources I found generally seemed to suggest that for lower case text there was a "right" letter spacing and that deviations in either direction resulted in decreased readability. The notion that you can improve readability by increasing letter spacing would then depend on whether the baseline letter spacing is the "right" one. If the font is properly designed, its letter spacing should be the right one, and modifying the letter spacing will make it worse. If increasing letter spacing always makes fonts easier to read it raises a question about why all fonts are mis-designed so that they are harder to read than necessary.

There's another possibility, which is that increasing letter spacing helps people with dyslexia but hurts comprehension for people without. Has that been tested? Certainly, the examples I've seen labeled as "too much space" appear harder to read to me, not easier. (I am not dyslexic, and I read a LOT.) It is obviouis that increasing letter spacing will at some point decrease readability.

I looked for something about dyslexia and found this https://www.bdadyslexia.org.uk/advice/employers/creating-a-dyslexia-friendly-workplace/dyslexia-friendly-style-guide which is oddly hard to read for a site on readability. They claim that letter spacing of "35%" of average letter width makes text readable for dyslexics. I don't know what that means. If it means add 35% of the average letter width between every letter I'm pretty sure that will result in something dramatically less readable for non-dyslexics, because it will separate the letters too much and break up the word form. Reading does not involve looking at the individual letters.

"A study of 61 sixth-grade children, 29 of whom had dyslexia, found that students with dyslexia needed larger letter spacing to identify letters than do students without dyslexia." Reading is not "identifying letters". But it could suggest that only dyslexics benefit from increased letter spacing.

It appears that serif fonts are easier to read in print, but sans serif fonts are easier to read for dyslexics, so that appears to be an example where the thing best for accessibility is not the universal best. On screens, it appears that due to limitations of resolution, sans serif fonts have generally been found easier to read. (I wonder if this is still true on modern smartphones whose dpi resolution can be 460 ppi.) I know that I have a personal preference for reading serif fonts in print, but I'm not sure if that's just due to familiarity or what.

In the OpenSCAD context, there is the question of what capabilities we should actually provide. If fonts are mis-designed or being abused because of how they are scaled, then tracking may be advantageous. There may also be some artificial reasons to increase tracking, such as to space letters apart far enough so that low resolution 3d printing creates a clear separation. For this reason, supporting increased letter spacing may be sensible. But it would need to be done "manually" rather than through the font engine, and would break ligatures. There are plenty of fonts without ligatures, though. I do wonder about how ligatures and tracking are expected to interact. Nobody ever mentions ligatures when discussing tracking. The other question is, what is the unit to use for specifying the space to add. Should it be absolute units, or relative to the font size? Nobody is very clear on the right way to specify tracking. And a "percentage of average letter width" is a pretty horrible measure to try to apply, especially when generalizing to Chinese, or thai.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 26, 2025

From my perspective (making fonts for 3D printing) letter spacing is totally irrelevant to readability, and 100% relevant to printability. The default spacing, especially for small fonts, is always too small, like less than a line width, which is really bad for printing engraved text in the top or bottom of a part. It matters also when printing text on the side of a part, to prevent characters from running together.

In any case, I think it is best to let OpenSCAD's internal text spacing to take care of things. That's what my word wrapping algorithm does already, you specify a letter spacing other than 1.0 if you want, and the textmetrics account for it. If it breaks ligatures, I don't know, and it's none of my business to know or care, I assume OpenSCAD handles this with spacings greater than 1. This has worked very well for me so far in other projects where I had to wrap text around objects, it just comes out well with spacing=1.1 usually.

I get the point about holding off on vertical word-wrapping; some vertical Japanese text has no spaces at all, even though it may have some punctuation. Other vertical text I've seen does use spaces, and these would make sense to word-wrap.

@adrianVmariano
Copy link
Collaborator

You absolutely cannot let OpenSCAD's internal "spacing" argument take care of anything. We shouldn't even pass through to that argument. It's completely broken, absolute garbage. It adds space that is proportional to the letter that occurred before. This is WRONG, it looks terrible, isn't readable, and doesn't fit the 3d printing needs either, where you'd want to add an extrusion width gap or something like that. Jordan was mystified by this implementation and can't imagine why anybody would have implemented it that way. Do you not think that "timing" looks odd in the above example I posted? To me it reads as 3 words, "Tim", "in" and "g".

The space added between letters needs to be constant. So if letterspacing adjustment is to be done, it needs to be done manually by the code, not relying on OpenSCAD's broken implementation.

It may be the case that given 3d printing needs, the right way to specify additional space would be in absolute units rather than in font-dependent units. I'm not sure. Perhaps both options could be permitted.

@Jasonkoolman
Copy link

Jasonkoolman commented Jan 27, 2025

@adrianVmariano You’re probably right that the use cases for letter spacing are exceptions to the rule. I’ve also come across contradictory information online, and it’s possible that we avoid applying letter spacing exclusively to lowercase letters—I’d need to confirm with the designers.

For what it’s worth, I came across a similar discussion about multiline text on the OpenSCAD GitHub repo. I’ve written a simple write module that can render multiline text. It’s straightforward but might meet the needs of many. You can check it out here: openscad/openscad#5018 (comment). It supports line height and spacing arguments for what its worth.

@amatulic
Copy link
Contributor Author

@Jasonkoolman for 3D printing applications I have always needed to apply letter spacing, because by default, text is too close together to resolve properly on a 3D printer. Letter spacing was implemented in this PR as using OpenSCAD's internal spacing, which Adrian has convinced me is the wrong approach, and I'm fixing that up today.

This PR also handles multi-line text -- it has to, because the original point of this PR was to provide automatic word-wrapping -- with left, right, center, and full justification. As is, this PR could be merged into BOSL2 to add a new capability without affecting anything else, but now we've decided to replace BOSL2's existing text() features with a new more robust write() feature that would include wordwrapping, and fix OpenSCAD's spacing issues while we're at it.

A minor complication to all this is the need to support the obsolete 2021.01. I wish OpenSCAD would have another stable release. I still use 2021.01 for error verification but I rarely encounter anyone else who uses it in everyday work.

@Jasonkoolman
Copy link

Jasonkoolman commented Jan 27, 2025

@amatulic That could very well be true. I’ve built quite a few scripts involving text (the latest being StampMaker, which is currently trending). So far, they seem to work well with the default letter spacing for 3D printing.

A minor complication to all this is the need to support the obsolete 2021.01. I wish OpenSCAD would have another stable release. I still use 2021.01 for error verification but I rarely encounter anyone else who uses it in everyday work.

I agree. This is a frustrating limitation that hampers innovation. While I truly enjoy working with OpenSCAD, the slow progress and lack of modern package management, roadmapping, and efficient release cycles are concerning. Still, it doesn’t stop us from pushing the boundaries and creating some incredible projects, which this library is certainly doing.

Your write module looks promising, and I’ll definitely use it once it’s released. One feature I’d love to see is the ability to retrieve the bounding box or the total width/height of a "text block." This would be invaluable for auto-adjusting dimensions based on text size—for example, fitting text perfectly onto the surface of a cube.

@amatulic
Copy link
Contributor Author

@Jasonkoolman - well with large fonts like in that stamp, I agree the default spacing is fine. Most of my projects involve small fonts down to 4 mm, and I even have a font I customized to help me print that small.

Another complication here is feature creep for things I hadn't planned but I think are a good idea, like instead of specifying a font size, you specify a box dimension and some text, with a margin, and the function finds the font size and positioning to fit the text the box with that margin, wrapping if needed.

@adrianVmariano
Copy link
Collaborator

@Jasonkoolman It may take a bit longer, but I'm confident that we'll have a nice capability once this is ready. I think it's OK to make the new function available only in OpenSCAD versions that have textmetrics.

@amatulic
Copy link
Contributor Author

it's OK to make the new function available only in OpenSCAD versions that have textmetrics.

Thank you. That makes things tidier.

@adrianVmariano
Copy link
Collaborator

There's too much of this that just wouldn't work without textmetrics. Just have it throw an error with a message to the effect that write() is only supported in OpenSCAD versions that have textmetrics, versions XXX and above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants