To automate choice of font-size (to exactly fill a box, for example) string length will suffice for monotyped fonts, but for most fonts we need font-specific metrics for particular strings (an i
is always narrower than an M
)
In an Execute JavaScript for Automation action, we can write:
// helvetica12Width :: String -> Num
function helvetica12Width(str) {
return $.NSAttributedString.alloc.init.initWithString(
str
)
.size.width;
}
to get metrics for a particular string in the default Helvetica 12. What I haven’t yet managed to do is to pass in attributes for other fonts and font sizes, and get corresponding metrics for those.
Has anyone discovered an idiom/syntax that works here from JXA or AppleScript ?
https://developer.apple.com/reference/foundation/nsattributedstring?language=objc
Update: This is the kind of thing I have experimented with – clearly off the mark though, as variation in the font size/name values doesn’t affect the return value:
(() => {
'use strict';
ObjC.import('AppKit');
return $.NSAttributedString.alloc.init.initWithStringAttributes(
"Substantiation", {
'NSFontAttributeName': $.NSFont.fontWithNameSize('Helvetica', 24)
}
)
.size.width
})();
1 Like
Ah … this seems to to do it:
(function () {
'use strict';
ObjC.import('AppKit');
// show :: a -> String
function show(x) {
return JSON.stringify(x, null, 2);
}
// stringSizeInFontAtPointSize :: String -> String -> Num
// -> {width:Num, height:Num}
function stringSizeInFontAtPointSize(str, fontName, points) {
return $.NSAttributedString.alloc.init.initWithStringAttributes(
str, $({
'NSFont': $.NSFont.fontWithNameSize(fontName, points)
})
)
.size;
}
// TEST -------------------------------------------------------------------
return show([
stringSizeInFontAtPointSize("hello World", "Geneva", 32),
stringSizeInFontAtPointSize("hello World", "Geneva", 64),
stringSizeInFontAtPointSize("hello World", "Helvetica", 64),
]);
})();
[
{
"width": 171.015625,
"height": 40
},
{
"width": 342.03125,
"height": 80
},
{
"width": 319,
"height": 78
}
]
1 Like
And, as a footnote, for iterative box-fitting of an unwrapped line of text:
(ES6 version, Sierra only)
(() => {
'use strict';
ObjC.import('AppKit');
// until :: (a -> Bool) -> (a -> a) -> a -> a
const until = (p, f, x) => {
let v = x;
while (!p(v)) v = f(v);
return v;
};
// pointSizeToFitSingleLineInBox :: Num -> Num -> String ->
// String -> Num -> Num -> Num
function pointSizeToFitLineInBox(boxWidth, boxHeight, fontName, strText,
minPoints, maxPoints) {
const
nMin = minPoints || 5,
nMax = maxPoints || 100,
mas = $.NSMutableAttributedString.alloc.init
.initWithStringAttributes(
strText, $({
'NSFont': $.NSFont.fontWithNameSize(fontName, nMin)
})
),
floor = Math.floor,
dctRange = {
location: 0,
length: strText.length
};
let dctSize = mas.size;
return until(
x => x.highest - x.lowest <= 1,
x => {
const
blnOver = (dctSize.width > boxWidth ||
dctSize.height > boxHeight),
upper = blnOver ? x.pointSize : x.highest,
lower = blnOver ? x.lowest : x.pointSize,
pSize = lower + floor((upper - lower) / 2);
// ITERATIVE MUTATION ---------------------------------
mas.setAttributesRange($({
'NSFont': $.NSFont.fontWithNameSize(fontName, pSize)
}), dctRange);
dctSize = mas.size;
// --------------------------------------------------------
return {
highest: upper,
lowest: lower,
pointSize: pSize
};
}, {
highest: nMax,
lowest: nMin,
pointSize: nMin
}
)
.pointSize;
}
// Point size to fit Helvetica 'Hello World !' in 500pts * 50pts space
return pointSizeToFitLineInBox(500, 50, "Helvetica", "Hello World !");
// -> 41
})();
1 Like