How to determine width of text in OpenSCAD?
Asked Answered
G

4

7

In OpenSCAD, I want to be able to create a module which accepts a string then create a 3-D object with that string embedded in the surface as a text. I want the object to be slightly larger than the text, so I need to know how wide the text is in order to create a properly-sized object.

I'm not sure how to query the width of the text (the height is set by an input variable), or if that's even possible.

If it's not possible, is there a function that will accept a string and a font and predict the width of the rendered text?

Giguere answered 19/9, 2016 at 1:18 Comment(0)
S
8

If you use one of the Liberation fonts bundled with OpenSCAD or the fonts in the Microsoft Core Fonts pack, you can use my font measurement OpenSCAD library. E.g.:

use <fontmetrics.scad>;
length = measureText("This is a Test", font="Arial:style=Italic", size=20.);

The library is here. I used some Python scripts to extract metrics (including kerning pairs) from the ttf file, and you can use the scripts to add information about more fonts.

Selfsealing answered 15/7, 2018 at 0:44 Comment(2)
Running the demo I get 5 warnings "WARNING: undefined operation (vector >= string) in file fontmetrics.scad, line 50". (But they're only warnings...)Kook
When I made the code, I was using a version of OpenSCAD() that had no is_string() function. You can replace function _isString(v) = v >= ""; with function _isString(v) = is_string(v);Selfsealing
R
5

There is currently no way to query the actual size of the generated text geometry. However, depending on the model that shall be created, it might be enough to calculate a rough estimation and use scale() to fit the text into the known size.

// Fit text into a randomly generated area

r = rands(10, 20, 2);
length = 3 * r[0];
w = r[1];

difference() {
    cube([length, w, 1]);
    color("white")
        translate([0, w / 2, 0.6])
            linear_extrude(1, convexity = 4)
                resize([length, 0], auto = true)
                    text("This is a Test", valign = "center");
}
Ray answered 19/9, 2016 at 1:46 Comment(1)
Using this answer as a guide, I created the model that constrains the width and height of a text label to the known width and height of a cube. thingiverse.com/thing:2090732Indraft
C
3

Update (2024) The original question, and all answers, have been superseded by the textmetrics() function that now exists in the daily builds of OpenSCAD.

I have found a way to determine the widths of text characters in OpenSCAD. I made a JavaScript thing that lets you input a font name and style, and it outputs an array of width proportions for ascii and extended ascii characters (codes 0-255). Then for any given character, you multiply this proportion by the font size to get the width of an individual character. From there it's trivial to get the width of a string, or the angular widths of characters wrapped around a cylinder.

The tool to generate the OpenSCAD width array is here: https://codepen.io/amatulic/pen/eYeBLva

...and the code is reproduced below, which you can run from this reply, or paste into your own HTML file and load into your browser locally.

Just input the font properties, click the button, and scroll down to see usage instructions.

The secret sauce lies in the fact that JavaScript's 'canvas' support has a 'measureText()' method that measures the pixel length of any text for a given font, used like this:

canvasContext.measureText(string).width

So what this code does is use a dummy canvas on the page to get a context to which a font is assigned, arbitrarily 20 pixels in size. Then it generates an array of widths for every character from 0 to 255, dividing each by 20 to get a unitless width proportion compared to font size. It then outputs a line of OpenSCAD code that you can paste into your OpenSCAD script. Then you use OpenSCAD's ord() function to convert any character to a numeric code, which then serves as an index of the width array. You then multiply this width by the font size to get the character width.

<html>
<!--
by Alex Matulich, February 2022
Thingiverse: https://www.thingiverse.com/amatulic/designs
Website: https://www.nablu.com
-->
<head>
  <script type="text/javascript">
    var sctx;

    function initialize() {
      var canvas = document.getElementById("canvas");
      sctx = canvas.getContext("2d");
    }

    function charwidth(fontname, style) {
      sctx.font = (style + " 20px " + fontname).trim();
      var charlen = [];
      for (i = 0; i < 256; ++i) //{ charlen[i] = 10; console.log(i); }
        charlen[i] = sctx.measureText(String.fromCharCode(i)).width / 20;
      return charlen;
    }

    function generate() {
      var fontname = document.getElementById("fontname").value;
      var fontstyle = document.getElementById("fontstyle").value;
      var widths = charwidth(fontname, fontstyle);
      var arrayname = toCamelCase(fontname) + toCamelCase(fontstyle);
      var outputhtml = arrayname + " = [<br/>\n" + widths[0].toString();
      var len = widths.length;
      for (i = 1; i < len; ++i) outputhtml += ', ' + widths[i].toString();
      outputhtml += "<br/>\n];\n";
      document.getElementById("output").innerHTML = outputhtml;
      document.getElementById('usage').innerHTML = "<h3>Usage</h3>\n<p>The array above shows character width as a multiple of font size. To get the width of a character <code>&lt;char&gt;</code> given font size <code>&lt;fontsize&gt;</code> using the font \"" + fontname + " " + fontstyle + "\":</p>\n<p><code>&nbsp; charwidth = " + arrayname + "[ord(char)] * fontsize;<code></p>\n";
      document.getElementById('sample').innerHTML = "<h3>Font sample</h3>\n<p style=\"font: " + fontstyle + " 20px " + fontname + ";\">" + fontname + " " + fontstyle + ": 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz</p>\n";
    }
    // convert the input array to camel case
    function toCamelCase(stringinput) {
      if (stringinput.length == 0) return '';
      var inputArray = stringinput.match(/[A-Z\xC0-\xD6\xD8-\xDE]?[a-z\xDF-\xF6\xF8-\xFF]+|[A-Z\xC0-\xD6\xD8-\xDE]+(?![a-z\xDF-\xF6\xF8-\xFF])|\d+/g);
      result = "";
      for (let i = 0, len = inputArray.length; i < len; i++) {
        let currentStr = inputArray[i];
        let tempStr = currentStr.toLowerCase();
        // convert first letter to upper case (the word is in lowercase)
        tempStr = tempStr.substr(0, 1).toUpperCase() + tempStr.substr(1);
        result += tempStr;
      }
      return result;
    }
  </script>
</head>

<body onload="initialize()">
  <h1>OpenSCAD proportional font widths</h1>
  <form>
    <fieldset>
      <legend>Identify the font</legend>
      <input type="text" id="fontname" name="fontname" value="Liberation Sans">
      <label for="fontname">Font name</label><br />
      <input type="text" id="fontstyle" name="fontstyle" value="bold">
      <label for="fontstyle">Font style (bold, italic, etc. or leave blank)<br />
    </fieldset>
    <input type="button" onclick="generate()" value="Generate OpenSCAD font width proportions">
  </form>
  <h2>Copy and paste this code into OpenSCAD</h2>
  <div id="output" style="border:5px ridge silver; padding:1em; font-family:monospace;">
  </div>
  <div id="usage">
  </div>
  <div id="sample">
  </div>
  <canvas id="canvas"></canvas>
</body>
</html>
Clothilde answered 7/2, 2022 at 6:2 Comment(2)
This is great. But it doesn't work, the loop in generate() should be from 1 since the first element was already added in the outputhtml variable initialization.Alan
Woops, you're correct. I have corrected this above, and in codepen. Thanks!Clothilde
C
0

The daily builds of OpenSCAD (not the last 2021.01 release) now include a textmetrics() function, which must be enabled in Edit / Preferences / Features. I have used it and it works quite well.

Clothilde answered 17/6 at 6:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.