How can I get the optical bounds of an NSAttributedString?
I need the optical bounds of an attributed string. I know I can call the .size() method and read its width but this obviously gives me typographic bounds with additional space to the right.
My strings would all be very short and consist only of 1-3 characters, so every string would contain exactly one glyphrun.
I found the function CTRunGetImageBounds, and after following the hints in the link from the comment I was able to extract the run and get the bounds, but obviously this does not give me the desired result.
The following swift 4 code works in an XCode9 Playground:
import Cocoa
import PlaygroundSupport
public func getGlyphWidth(glyph: CGGlyph, font: CTFont) -> CGFloat
var glyph = glyph
var bBox = CGRect()
CTFontGetBoundingRectsForGlyphs(font, .default, &glyph, &bBox, 1)
return bBox.width
class MyView: NSView
init(inFrame: CGRect)
super.init(frame: inFrame)
required init?(coder decoder: NSCoder)
fatalError("init(coder:) has not been implemented")
override func draw(_ rect: CGRect)
// setup context properties
let context: CGContext = NSGraphicsContext.current!.cgContext
context.setStrokeColor(CGColor.black)
context.setTextDrawingMode(.fill)
// prepare variables and constants
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L"]
let font = CTFontCreateWithName("Helvetica" as CFString, 48, nil)
var glyphX: CGFloat = 10
// draw alphabet as single glyphs
for letter in alphabet
var glyph = CTFontGetGlyphWithName(font, letter as CFString)
var glyphPosition = CGPoint(x: glyphX, y: 80)
CTFontDrawGlyphs(font, &glyph, &glyphPosition, 1, context)
glyphX+=getGlyphWidth(glyph: glyph, font: font)
let textStringAttributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.font : font,
]
glyphX = 10
// draw alphabet as attributed strings
for letter in alphabet
let textPosition = NSPoint(x: glyphX, y: 20)
let text = NSAttributedString(string: letter, attributes: textStringAttributes)
let line = CTLineCreateWithAttributedString(text)
let runs = CTLineGetGlyphRuns(line) as! [CTRun]
let width = (CTRunGetImageBounds(runs[0], nil, CFRange(location: 0,length: 0))).maxX
text.draw(at: textPosition)
glyphX += width
var frameRect = CGRect(x: 0, y: 0, width: 400, height: 150)
PlaygroundPage.current.liveView = MyView(inFrame: frameRect)
The code draws the single letters from A - L as single Glyphs in the upper row of the playground's live view. The horizontal position will be advanced after each letter by the letter's width which is retrieved via the getGlyphWidth function.
Then it uses the same letters to create attributed strings from it which will then be used to create first a CTLine, extract the (only) CTRun from it and finally measure its width. The result is seen in the second line in the live view.
The first line is the desired result: The width function returns exactly the width of every single letter, resulting in them touching each other.
I want the same result with the attributed string version, but here the ImageBounds seem to add an additional padding which I want to avoid.
How can I measure the exact width from the leftmost to the rightmost pixel of a given text?
And is there a less clumsy way to achieve this without having to cast four times (NSAtt.Str->CTLine->CTRun->CGRect->maxX) ?
swift nsattributedstring bounds
add a comment |
I need the optical bounds of an attributed string. I know I can call the .size() method and read its width but this obviously gives me typographic bounds with additional space to the right.
My strings would all be very short and consist only of 1-3 characters, so every string would contain exactly one glyphrun.
I found the function CTRunGetImageBounds, and after following the hints in the link from the comment I was able to extract the run and get the bounds, but obviously this does not give me the desired result.
The following swift 4 code works in an XCode9 Playground:
import Cocoa
import PlaygroundSupport
public func getGlyphWidth(glyph: CGGlyph, font: CTFont) -> CGFloat
var glyph = glyph
var bBox = CGRect()
CTFontGetBoundingRectsForGlyphs(font, .default, &glyph, &bBox, 1)
return bBox.width
class MyView: NSView
init(inFrame: CGRect)
super.init(frame: inFrame)
required init?(coder decoder: NSCoder)
fatalError("init(coder:) has not been implemented")
override func draw(_ rect: CGRect)
// setup context properties
let context: CGContext = NSGraphicsContext.current!.cgContext
context.setStrokeColor(CGColor.black)
context.setTextDrawingMode(.fill)
// prepare variables and constants
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L"]
let font = CTFontCreateWithName("Helvetica" as CFString, 48, nil)
var glyphX: CGFloat = 10
// draw alphabet as single glyphs
for letter in alphabet
var glyph = CTFontGetGlyphWithName(font, letter as CFString)
var glyphPosition = CGPoint(x: glyphX, y: 80)
CTFontDrawGlyphs(font, &glyph, &glyphPosition, 1, context)
glyphX+=getGlyphWidth(glyph: glyph, font: font)
let textStringAttributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.font : font,
]
glyphX = 10
// draw alphabet as attributed strings
for letter in alphabet
let textPosition = NSPoint(x: glyphX, y: 20)
let text = NSAttributedString(string: letter, attributes: textStringAttributes)
let line = CTLineCreateWithAttributedString(text)
let runs = CTLineGetGlyphRuns(line) as! [CTRun]
let width = (CTRunGetImageBounds(runs[0], nil, CFRange(location: 0,length: 0))).maxX
text.draw(at: textPosition)
glyphX += width
var frameRect = CGRect(x: 0, y: 0, width: 400, height: 150)
PlaygroundPage.current.liveView = MyView(inFrame: frameRect)
The code draws the single letters from A - L as single Glyphs in the upper row of the playground's live view. The horizontal position will be advanced after each letter by the letter's width which is retrieved via the getGlyphWidth function.
Then it uses the same letters to create attributed strings from it which will then be used to create first a CTLine, extract the (only) CTRun from it and finally measure its width. The result is seen in the second line in the live view.
The first line is the desired result: The width function returns exactly the width of every single letter, resulting in them touching each other.
I want the same result with the attributed string version, but here the ImageBounds seem to add an additional padding which I want to avoid.
How can I measure the exact width from the leftmost to the rightmost pixel of a given text?
And is there a less clumsy way to achieve this without having to cast four times (NSAtt.Str->CTLine->CTRun->CGRect->maxX) ?
swift nsattributedstring bounds
Check this stackoverflow.com/a/33425181/771231
– Desdenova
Nov 13 '18 at 13:29
add a comment |
I need the optical bounds of an attributed string. I know I can call the .size() method and read its width but this obviously gives me typographic bounds with additional space to the right.
My strings would all be very short and consist only of 1-3 characters, so every string would contain exactly one glyphrun.
I found the function CTRunGetImageBounds, and after following the hints in the link from the comment I was able to extract the run and get the bounds, but obviously this does not give me the desired result.
The following swift 4 code works in an XCode9 Playground:
import Cocoa
import PlaygroundSupport
public func getGlyphWidth(glyph: CGGlyph, font: CTFont) -> CGFloat
var glyph = glyph
var bBox = CGRect()
CTFontGetBoundingRectsForGlyphs(font, .default, &glyph, &bBox, 1)
return bBox.width
class MyView: NSView
init(inFrame: CGRect)
super.init(frame: inFrame)
required init?(coder decoder: NSCoder)
fatalError("init(coder:) has not been implemented")
override func draw(_ rect: CGRect)
// setup context properties
let context: CGContext = NSGraphicsContext.current!.cgContext
context.setStrokeColor(CGColor.black)
context.setTextDrawingMode(.fill)
// prepare variables and constants
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L"]
let font = CTFontCreateWithName("Helvetica" as CFString, 48, nil)
var glyphX: CGFloat = 10
// draw alphabet as single glyphs
for letter in alphabet
var glyph = CTFontGetGlyphWithName(font, letter as CFString)
var glyphPosition = CGPoint(x: glyphX, y: 80)
CTFontDrawGlyphs(font, &glyph, &glyphPosition, 1, context)
glyphX+=getGlyphWidth(glyph: glyph, font: font)
let textStringAttributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.font : font,
]
glyphX = 10
// draw alphabet as attributed strings
for letter in alphabet
let textPosition = NSPoint(x: glyphX, y: 20)
let text = NSAttributedString(string: letter, attributes: textStringAttributes)
let line = CTLineCreateWithAttributedString(text)
let runs = CTLineGetGlyphRuns(line) as! [CTRun]
let width = (CTRunGetImageBounds(runs[0], nil, CFRange(location: 0,length: 0))).maxX
text.draw(at: textPosition)
glyphX += width
var frameRect = CGRect(x: 0, y: 0, width: 400, height: 150)
PlaygroundPage.current.liveView = MyView(inFrame: frameRect)
The code draws the single letters from A - L as single Glyphs in the upper row of the playground's live view. The horizontal position will be advanced after each letter by the letter's width which is retrieved via the getGlyphWidth function.
Then it uses the same letters to create attributed strings from it which will then be used to create first a CTLine, extract the (only) CTRun from it and finally measure its width. The result is seen in the second line in the live view.
The first line is the desired result: The width function returns exactly the width of every single letter, resulting in them touching each other.
I want the same result with the attributed string version, but here the ImageBounds seem to add an additional padding which I want to avoid.
How can I measure the exact width from the leftmost to the rightmost pixel of a given text?
And is there a less clumsy way to achieve this without having to cast four times (NSAtt.Str->CTLine->CTRun->CGRect->maxX) ?
swift nsattributedstring bounds
I need the optical bounds of an attributed string. I know I can call the .size() method and read its width but this obviously gives me typographic bounds with additional space to the right.
My strings would all be very short and consist only of 1-3 characters, so every string would contain exactly one glyphrun.
I found the function CTRunGetImageBounds, and after following the hints in the link from the comment I was able to extract the run and get the bounds, but obviously this does not give me the desired result.
The following swift 4 code works in an XCode9 Playground:
import Cocoa
import PlaygroundSupport
public func getGlyphWidth(glyph: CGGlyph, font: CTFont) -> CGFloat
var glyph = glyph
var bBox = CGRect()
CTFontGetBoundingRectsForGlyphs(font, .default, &glyph, &bBox, 1)
return bBox.width
class MyView: NSView
init(inFrame: CGRect)
super.init(frame: inFrame)
required init?(coder decoder: NSCoder)
fatalError("init(coder:) has not been implemented")
override func draw(_ rect: CGRect)
// setup context properties
let context: CGContext = NSGraphicsContext.current!.cgContext
context.setStrokeColor(CGColor.black)
context.setTextDrawingMode(.fill)
// prepare variables and constants
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L"]
let font = CTFontCreateWithName("Helvetica" as CFString, 48, nil)
var glyphX: CGFloat = 10
// draw alphabet as single glyphs
for letter in alphabet
var glyph = CTFontGetGlyphWithName(font, letter as CFString)
var glyphPosition = CGPoint(x: glyphX, y: 80)
CTFontDrawGlyphs(font, &glyph, &glyphPosition, 1, context)
glyphX+=getGlyphWidth(glyph: glyph, font: font)
let textStringAttributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.font : font,
]
glyphX = 10
// draw alphabet as attributed strings
for letter in alphabet
let textPosition = NSPoint(x: glyphX, y: 20)
let text = NSAttributedString(string: letter, attributes: textStringAttributes)
let line = CTLineCreateWithAttributedString(text)
let runs = CTLineGetGlyphRuns(line) as! [CTRun]
let width = (CTRunGetImageBounds(runs[0], nil, CFRange(location: 0,length: 0))).maxX
text.draw(at: textPosition)
glyphX += width
var frameRect = CGRect(x: 0, y: 0, width: 400, height: 150)
PlaygroundPage.current.liveView = MyView(inFrame: frameRect)
The code draws the single letters from A - L as single Glyphs in the upper row of the playground's live view. The horizontal position will be advanced after each letter by the letter's width which is retrieved via the getGlyphWidth function.
Then it uses the same letters to create attributed strings from it which will then be used to create first a CTLine, extract the (only) CTRun from it and finally measure its width. The result is seen in the second line in the live view.
The first line is the desired result: The width function returns exactly the width of every single letter, resulting in them touching each other.
I want the same result with the attributed string version, but here the ImageBounds seem to add an additional padding which I want to avoid.
How can I measure the exact width from the leftmost to the rightmost pixel of a given text?
And is there a less clumsy way to achieve this without having to cast four times (NSAtt.Str->CTLine->CTRun->CGRect->maxX) ?
swift nsattributedstring bounds
swift nsattributedstring bounds
edited Nov 13 '18 at 19:42
MassMover
asked Nov 13 '18 at 13:17
MassMoverMassMover
538
538
Check this stackoverflow.com/a/33425181/771231
– Desdenova
Nov 13 '18 at 13:29
add a comment |
Check this stackoverflow.com/a/33425181/771231
– Desdenova
Nov 13 '18 at 13:29
Check this stackoverflow.com/a/33425181/771231
– Desdenova
Nov 13 '18 at 13:29
Check this stackoverflow.com/a/33425181/771231
– Desdenova
Nov 13 '18 at 13:29
add a comment |
1 Answer
1
active
oldest
votes
Ok, I found the answer myself:
Using the .width parameter of the CTRunGetImageBounds instead of .maxX brings the right result
The same function also does exist for the CTLine: CTLineGetImageBounds
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53281882%2fhow-can-i-get-the-optical-bounds-of-an-nsattributedstring%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Ok, I found the answer myself:
Using the .width parameter of the CTRunGetImageBounds instead of .maxX brings the right result
The same function also does exist for the CTLine: CTLineGetImageBounds
add a comment |
Ok, I found the answer myself:
Using the .width parameter of the CTRunGetImageBounds instead of .maxX brings the right result
The same function also does exist for the CTLine: CTLineGetImageBounds
add a comment |
Ok, I found the answer myself:
Using the .width parameter of the CTRunGetImageBounds instead of .maxX brings the right result
The same function also does exist for the CTLine: CTLineGetImageBounds
Ok, I found the answer myself:
Using the .width parameter of the CTRunGetImageBounds instead of .maxX brings the right result
The same function also does exist for the CTLine: CTLineGetImageBounds
answered Nov 13 '18 at 20:58
MassMoverMassMover
538
538
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53281882%2fhow-can-i-get-the-optical-bounds-of-an-nsattributedstring%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Check this stackoverflow.com/a/33425181/771231
– Desdenova
Nov 13 '18 at 13:29