Reputation: 405
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?
Upvotes: 7
Views: 7117
Reputation: 1102
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.
Upvotes: 2
Reputation: 1102
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><char></code> given font size <code><fontsize></code> using the font \"" + fontname + " " + fontstyle + "\":</p>\n<p><code> 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>
Upvotes: 4
Reputation: 434
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.
Upvotes: 9
Reputation: 494
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");
}
Upvotes: 5