Calculating height of NSString with custom font - objective-c

I'm trying to solve this problem for a week now and I have tested a lot of ideas that I had on my mind but I'm unable to properly calculate size of the NSString with custom font.
I have UITextView which contains text and one UIView on which I draw line numbers for the lines in UITextView. The problem is that NSString UIKit Additions are ignoring tab width in calculation of size of the NSString.
Here on picture you can see that clearly on line 7, which is line with line break when rendered in UITextView, and after that all lines are affected.
Font that I'm using is Adobe Source Code Pro.
I have tried all methods from NSString UIKit Additions without success.
sizeWithFont
sizeWithFont:forWidth:lineBreakMode:
sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:
I have also tried to replace all tabs in string with four spaces, it helps, but still it doesn't work all the time. For some lines it helps but for some it doesn't.
Any suggestions how to calculate NSString height properly? CoreText maybe?
One small note. I have tried to solve this also with using Geometry hit testing methods from UITextInput Protocol and while they are working, cpu load is 100% on simulator, so on real device it's going to kill the app, specially if I load file that is about 1500+ lines of code.
And here is the gist with code that I'm using for LineNumbersView.m.

Assuming UITextView and Core Text always exactly agree then the latter is definitely a solution, though it's not desperately straightforward for this problem.
You'd:
create an attributed string of the text in the main view, having set the appropriate font across the entire range;
create a suitable framesetter for the attributed string (using CTFramesetterCreateWithAttributedString);
create a CGPath that describes the drawing area that the string will be drawn to (either as a UIBezierPath and then getting the CGPath property or by creating one and using CGPathAddRect);
hence create a frame from the framesetter plus the path plus the subrange of the string you're interested in, which will likely be all of it (see CTFramesetterCreateFrame);
from the frame you can then obtain a CFArray of the individual lines it would output (using CTFrameGetLines), though these are the on-screen typeset lines rather than your source lines;
for each line you can get the range of the original string that it contains (using CTLineGetStringRange), allowing you to determine which begin immediately after your original newline characters rather than due to word wrap;
you can also get a C array of CGPoints where each represents the origin of an on-screen line (CTFrameGetLineOrigins); by using what you learn in (6) to look up positions in that you can get the on-screen origins of the relevant lines.
For added fun, the pixel output of Core Text is identical on OS X and on iOS. However OS X considers the screen's original to be the lower left corner, like graph paper. iOS considers it to the top left corner, like English reading order. The roughly worded net effect is that on iOS Core Text draws upside down. So you'll need to take that into account. In iOS terms you'll apparently see the first line as being at the very bottom of your frame, the second as being one line above that, etc. You'll need to flip those coordinates.
It'll probably end up being just a hundred or so lines of code even though you're jumping through so many hoops.

Related

How to get line breaks in a long text when writing to a pdf using canvas

I am trying to write long texts (captured through string.xml in the $nameSpeech variable) to a pdf using canvas.drawText, but it does not implement any line breaks, and \n and more do not work. Is this possible anyway using canvas.drawText?
canvas.drawText("This was his speech: $nameSpeech",20F, 200F,title)
I am a rookie with Kotlin and studied several main developer sites, and tried to specify start and end drawText. There is a lot on Java but little on Kotlin. Who can help?
Nah, all the Canvas draw methods are fairly low-level operations to draw shapes at particular points, it doesn't do anything fancy with interpreting text as multiline, doing any wrapping etc.
You could break the lines yourself and render each one:
val lines = text.split('\n')
lines.forEachIndexed { i, line ->
drawText(line, x, y + (lineSpacing * i), paint)
}
lineSpacing is the height of your text - you can get a lot of this by setting your typeface and text size on your Paint and then accessing its functions like getFontSpacing(). There are also helper functions like breakText that can tell you how many characters of a String will fit a given width so you can handle line breaks, stuff like that.
Again, you're doing a lot of the work yourself here, working out widths and adjusting for spacing etc, breaking lines - TextView is a View that already handles all that work, so you could try just using that, and calling its draw method with a Canvas you want it to use. It needs to have been laid out already, if you don't want it to be visible then setting its visibility to INVISIBLE (not GONE) should be ok (I've never actually done it)
Another thing you could try is actually subclassing TextView, so maybe you could set its size internally, have its onDraw method call the superclass one passing your custom canvas, and have it work without needing to be in a layout. If you're new to all this that might be a bit complex (just because it's unfamiliar really) but it's something you could maybe look into!

NSTextField get default margin dimensions

I`ve got several programmatically created NSTextField-s, used as static text controls. The algorithm that creates them sets their dimensions via setFrame: and assumes that they have zero padding between the text and the upper right corner of the control, which is not the case, so the text is getting clipped.
At first I began researching the topic of zero padding for NSTextField-s but quickly realized that this approach requires subclassing. It would be much easier for me than that if I could just use some method to obtain the padding dimensions and adjust the bounding rectangle accordingly.
My question is: what`s the name of that method?
[UPD:] Seems to be alignmentRectInsets, however it`s not available on macOS 10.6 — so I`d like to know if there is a similar method for 10.6.

Pixel-perfect drawing in Cocoa on the retina display - just doing floor() won't work anymore

Historically, one has simply done a floor() after any calculations, to make sure all coordinates, heights and widths align properly to the pixel boundary.
However, this clearly won't work anymore on the retina display, because 0.5 point is now perfectly valid.
How should coders now code pixel perfect things to make their code properly support both standard and retina displays?
Convert the rect to backing-aligned coordinates. You'll probably be doing this in the view, but windows can do it and screens can do it as well.
You may also need to convert back, since the release notes suggest that the backing coordinate spaces are in pixels, so those spaces will obviously be twice as big on a Retina display. If I had one, I'd test it. If everything looks twice as big when you use the backing coordinates in view space, that means you do need to convert them back to view coordinates.

UIBezierPath subtraction from another UIBezierPath

I am making an app that allows the user to draw on the screen with his finger in different colors. The drawings are drawn with UIBezierPaths but I need an eraser. I did have an eraser that was just a path with the background image as the color but this method causes memory issues. I would like to delete the points from any path that is drawn on when eraser is selected.
Unfortunately UIBezierPath doesn't have a subtraction function so I want to make my own. So if eraser is selected, it will look at all the points that should be erased and see if any of the existing paths contain those points, then subdivide the path leaving a blank spot. But it should be able to see how many points in a row to delete not do it one at a time. In theory it makes sense but I'm having trouble getting started on the implementation.
Anyone have any guidance to set me on the right 'path'?
Upon first glance, it appears that you could do hit detection on a UIBezierPath by simply using containsPoint:. That works fine if you want to determine whether the point is contained in the fill of a UIBezierPath, but it does not work for determining whether only the stroke of the UIBezierPath intersects the point. Detecting whether or not a given point is in the stroke of a UIBezierPath can be done as described in the "Doing Hit-Detection on a Path" section at the bottom of this page. Actually, the code sample they give could be used either way. The basic idea is that you have to use the Core Graphics method CGContextPathContainsPoint.
Depending on how large the eraser brush is, you will probably want to check several different points on the edge of the brush circle to see if they intersect the curve, and you'll probably have to iterate through your UIBezierPaths until you get a hit. You should be able to optimize the search by using the bounds of the UIBezierPath.
After you detect that a point intersects a UIBezierPath, you must do the actual split of the path. There appears to be a good outline of the algorithm in this post. The main idea there is to use De Casteljau's algorithm to perform the subdivision of the curve. There are various implementations of the algorithm that you should be able to find with a quick search, including some in C++.

how to generate graphs using integer values in iphone

i want to show a grapph/bar chart in iphone how do i do this without custom API;s
You may want to investigate the Core Plot project [code.google.com]. Core Plot was the subject of this year's scientific coding project at WWDC and is pretty useable for some cases already. From its inception, Core Plot was intended for both OS X and iPhone uses. The source distribution (there hasn't been a binary release yet) comes with both OS X and iPhone example applications and there's info on the project wiki for using it as a library in an iPhone app. Here's an example of it's current plotting capabilities.
(source: googlecode.com)
Write your own. It's not easy, I'm in the process of doing the same thing right now. Here's how I'm doing it:
First, ignore any desire you may have to try using a UIScrollView if you want to allow zooming. It's totally not worth it.
Second, create something like a GraphElement protocol. I have a hierarchy that looks something like this:
GraphElement
GraphPathElement
GraphDataElement
GraphDataSupplierElement
GraphElement contains the basic necessary methods for a graph element, including how to draw, a maximum width (for zooming in), whether a point is within that element (for touches) and the standard touchBegan, touchMoved, and touchEnded functions.
GraphPathElement contains a CGPath, a line color and width, a fill color and a drawing mode. Whenever it's prompted to draw, it simply adds the path to the context, sets the colors and line width, and draws the path with the given drawing mode.
GraphDataElement, as a subclass of GraphPathElement, takes in a set of data in x-y coordinates, a graph type (bar or line), a frame, and a bounds. The frame is the actual size of the created output CGPath. The bounds is the size of the data in input coordinates. Essentially, it lets you scale the data to the screen size.
It creates a graph by first calculating an affine transform to transform the bounds to the frame, then it loops through each point and adds it as data to a path, applying that transform to the point before adding it. How it adds data depends on the type.
If it's a bar graph, it creates a rectangle of width 0, origin at (x,frame.size.height-y), and height=y. Then it "insets" the graph by -3 pixels horizontally, and adds that to the path.
If it's a line graph, it's much simpler. It just moves to the first point, then for each other point, it adds a line to that point, adds a circle in a rect around that point, then moves back to that point to go on to the next point.
GraphDataSupplierElement is the interface to my database that actually contains all the data. It determines what kind of graph it should be, formats the data into the required type for GraphDataElement, and passes it on, with the color to use for that particular graph.
For me, the x-axis is time, and is represented as NSTimeIntervals. The GraphDataSupplierElement contains a minDate and maxDate so that a GraphDateElement can draw the x-axis labels as required.
Once all this is done, you need to create the actual graph. You can go about it several ways. One option is to keep all the elements in an NSArray and whenever drawRect: is called, loop through each element and draw it. Another option is to create a CALayer for each element, and use the GraphPathElement as the CALayer's delegate. Or you could make GraphPathElement extend from CALayer directly. It's up to you on this one. I haven't gotten as far as trying CALayers yet, I'm still stuck in the simple NSArray stage. I may move to CALayers at some point, once I'm satisfied with how everything looks.
So, all in all, the idea is that you create the graph as one or many CGPaths beforehand, and just draw that when you need to draw the graph, rather than trying to actually parse data whenever you get a drawRect: call.
Scaling can be done by keeping the source data in your GraphDataElement, and just change the frame so that the scaling of the bounds to the frame creates a CGPath wider than the screen, or whatever your needs are. I basically re-implemented my own pinch-zoom for my Graph UIView subclass that only scales horizontally, by changing its transform, then on completion, get the current frame, reset the transform to identity, set the frame to the saved value, and set the frame of all of the GraphElements to the new frame as well, to make them scale. Then just call [self setNeedsDisplay] to draw.
Anyway, that's a bit ramble-ish, but it's an outline of how I made it happen. If you have more specific questions, feel free to comment.