Core Text returns absurd font bounding boxes for certain system fonts? - core-text

I am playing around with CoreText and note the function CTFontGetBoundingBox returns abnormally large height for certain fonts. Here is a snippet for Swift Playground that I retrieve these data:
import CoreText
extension CGFloat {
var string: String {
return String(format: "%.2f", self)
}
}
func getFontMetrics(fontName: String) -> (String, String, String, String) {
let font = CTFontCreateWithName(fontName as CFString, 50.0, nil)
return (CTFontGetDescent(font).string, CTFontGetAscent(font).string, CTFontGetLeading(font).string, CTFontGetBoundingBox(font).height.string)
}
getFontMetrics(fontName: "Times-Roman")
getFontMetrics(fontName: "TimesNewRomanPSMT")
getFontMetrics(fontName: "Zapfino")
getFontMetrics(fontName: "Helvetica")
getFontMetrics(fontName: "HelveticaNeue")
getFontMetrics(fontName: "PingFangSC-Regular")
Most data look fine, but the results for Times-Roman and Zapfino are way off the normal range, which are
(.0 "12.50", .1 "37.50", .2 "0.00", .3 "84.99")
(.0 "75.13", .1 "93.75", .2 "0.00", .3 "294.75")
(Times-Roman on top and Zapfino on bottom; from left to right: descent, ascent, leading / line gap, bounding box’s height)
As a comparison with normal data, here is what for TimesNewRomanPSMT:
(.0 "10.82", .1 "44.56", .2 "2.12", .3 "65.67")
I dumped some data from Zapfino, and it seems that the bounding box is close to usWinDescent plus usWinAscent:
<sTypoAscender value="677"/>
<sTypoDescender value="-514"/>
<sTypoLineGap value="0"/>
<usWinAscent value="750"/>
<usWinDescent value="1264"/>
(750+1264)/677 = 2.97, whereas 294.75/93.75 = 3.144, but I am not sure what is going on here exactly.
My question is: why sometimes bounding boxes’ behavior are so strange? Where do these data come from? Under which circumstances should I use CTFontGetBoundingBox?

There’s nothing abnormal with the results you get. That is exactly how font data works and Core Text returns all values as expected. The thing is that a font’s bounding box is intimately tied to its glyphs’ outlines, whereas the ascent, descent, and leading metrics define the font’s intended line spacing. These metrics are independent of the actual glyph outlines that may and often do exceed the line height in both directions. This can be seen in glyphs with more complex diacritical marks, for example. It’s also quite evident in the kind of non-utilitarian designs like Zapfino with its excessive calligraphic swashes. In such cases, the bounding box can differ from the line metrics considerably.

Related

Histogram of image not showing expected distribution

I have a cell array called output. Output contains matrices of size 1024 x 1024, type = double, grayscale. I would like to plot each matrix and its corresponding histogram on a single plot. Here is what I have so far:
for i = 1:size(output,2)
figure
subplot(2,1,1)
imagesc(output{1,i});
colormap('gray')
colorbar;
title(num2str(dinfo(i).name))
subplot(2,1,2)
[pixelCount, grayLevels] = imhist(output{1,i});
bar(pixelCount);
title('Histogram of original image');
xlim([0 grayLevels(end)]); % Scale x axis manually.
grid on;
end
The plot I get, however, seems to be faulty... I was expecting a distribution of bars.
I am somewhat lost at how to proceed, any help or suggestions would be appreciated!
Thanks :)
Based on the colorbar on your image plot the values of your image pixels range from [0, 5*10^6].
For many image processing functions, MATLAB assumes one of two color models, double values ranging from [0, 1] or integer values ranging from [0 255]. While the supported ranges are not explicitly mentioned in the imhist documentation, in the "Tips" section of the imhist documentation, there is a table of scale factors for different numeric types that hints at these assumptions.
I think the discrepancy between your image range and these models is the root of the problem.
For example, I load a grayscale image and scale the pixels by 1000 to approximate your data.
% Toy data to approximate your image
I = im2double(imread('cameraman.tif'));
output = {I, I .* 1000};
for i = 1:size(output,2)
figure
subplot(2,1,1)
imagesc(output{1,i});
colormap('gray')
colorbar;
subplot(2,1,2)
[pixelCount, grayLevels] = imhist(output{1,i});
bar(pixelCount);
title('Histogram of original image');
grid on;
end
The first image is using a matrix with the standard [0,1] double value range. The imhist calculates a histogram as expected. The second image is using a matrix with the scaled [0, 1000] double value range. imhist assigns all the pixels to the 255 bin since that is the maximum bin. Therefore, we need a method that allows us to scale the bins.
Solution : Use histogram
histogram is designed for any numeric type and range. You may need to fiddle with the bin edges to show the structures that you are interested in as it doesn't initialize bins the same way imhist does.
figure
subplot(2,1,1)
imagesc(output{1,2});
colormap('gray')
colorbar;
subplot(2,1,2)
histogram(output{1,2});
title('Histogram of original image');
grid on;

Plotting arrays using a grouped horizontal bar graph

I am trying to generate a graph that should look similar to:
My arrays are:
Array4:[Nan;Nan;.......;20;21;22;23;24;..........60]
Array3:[[Nan;Nan;.......;20;21;22;23;24;..........60]
Array2:[0;1;2;3;4;5;6;Nan;Nan;Nan;Nan;17;18;.....60]
Array1:[0;1;2;3;4;5;6;Nan;Nan;Nan;Nan;17;18;.....60]
I cannot find the right way to group my arrays in order to plot them in the way shown on the above graph.
I tried using the following function explained in: http://uk.mathworks.com/help/matlab/ref/barh.html
barh(1:numel(x),y,'hist')
where y=[Array1,Array2;Array3,Array4] and x={'1m';'2m';'3m';......'60m'}
but it does not work.
Why Your Current Approach Isn't Working
Your intuition makes sense to me, but the barh function you are using doesn't work the way you think it does. Specifically, you are interpreting the meaning of the x and y inputs to that function incorrectly. Those are inputs are constant values, not entire axes. The first y input refers to the end-point of the bar that stretches horizontally from x = 0 and the first x input refers to location on the y-axis of the horizontal bar. To illustrate what I mean, I've provided the below horizontal bar graph:
You can find this same picture in the official documentation of the MATLAB barh function. The code used to generate this bar graph is also given in the documentation, shown below:
x = 1900:10:2000;
y = [57,91,105,123,131,150,...
170,203,226.5,249,281.4];
figure;
barh(x, y);
The individual elements of the x array, rather confusingly, show up on the y-axis as the starting locations of each bar. The corresponding elements of the y array are the lengths of each bar. This is the reason that the arrays must be the same length, and this illustrates that they are not specifications of the x and y axes as one might intuitively believe.
An Approach To Solve Your Problem
First things first, the easiest approach is to do this manually with the plot function and a set of lines that represent floating bars. Consult the official documentation for the plot function if you'd like to plot the lines with some sort of color coordination in mind - the code I present (modified version of this answer on StackOverflow) just switches the color of the floating bars between red and blue. I tried to comment the code so that the purpose of each variable is clear. The code I present below matches the floating bar graph that you want to be plotted, if you are alright with replacing thick floating bars with 2D lines floating on a plot.
I used the data that you gave in your question to specify the floating horizontal bars that this script would output - a screenshot is shown below the code. Array1 & Array2:[0;1;2;3;4;5;6;Nan;Nan;Nan;Nan;17;18;.....60], these arrays go from 0 to 6 (length = 6) and 17 to 60 (length = 60 - 17 = 43). Because there is a "discontinuity" of sorts from 7 to 16, I have to define two floating bars for each array. Hence, the first four values in my length array are [6, 6, 43, 43]. Where the first 6 and the first 43 correspond to Array1 and the second 6 and the second 43 correspond to Array2. Recognizing this "discontinuity", the starting point of the first floating bar for Array1 and Array2 is x = 0 and the starting point of the second floating bar for Array1 and Array2 is x = 7. Putting that all together, you arrive at the x-coordinates for the first four points in the floating_bars array, [0 0; 0 1.5; 17 0; 17 1.5]. The y-coordinates in this array only serve to distinguish Array1, Array2, and so on from each other.
Code:
floating_bars=[0 0; 0 1.5; 17 0; 17 1.5; 20 6; 20 7.5]; % Each row is the [x,y] coordinate pair of the starting point for the floating bar
L=[6, 6, 43, 43, 40, 40]; % Length of each consecutive bar
thickness = 0.75;
figure;
for i=1:size(floating_bars,1)
curr_thickness = 0;
% It is aesthetically pleasing to have thicker bars, this makes the plot look for like the grouped horizontal bar graph that you want
while (curr_thickness < thickness)
% Each bar group has two bars; set the first to be red, the second to be blue (i.e., even index means red bar, odd index means blue bar)
if mod(i, 2)
plot([floating_bars(i,1), floating_bars(i,1)+L(i)], [floating_bars(i,2) + curr_thickness, floating_bars(i,2) + curr_thickness], 'r')
else
plot([floating_bars(i,1), floating_bars(i,1)+L(i)], [floating_bars(i,2) + curr_thickness, floating_bars(i,2) + curr_thickness], 'b')
end
curr_thickness = curr_thickness + 0.05;
hold on % Make sure that plotting the current floating bar does not overwrite previous float bars that have already been plotted
end
end
ylim([ -10 30]) % Set the y-axis limits so that you can see more clearly the floating bars that would have rested right on the x-axis (y = 0)
Output:
How Do I Do This With the barh Function?
The short answer is that you'd have to modify the function manually. Someone has already done this with one of the bar graph plotting functions provided by MATLAB, bar3. The logic implemented in this modified bar3 function can be re-applied for your purposes if you read their barNew.m function and tweak it a bit. If you'd like a pointer as to where to start, I'd suggest looking at how they specify z-axis minimum and maximums for their floating bars on the plot, and apply that same logic to specify x-axis minimum and maximums for your floating bars in your 2D case.
I hope this helps, happy coding! :)
I explain here my approach to generate these type of graphs. Not sure if it is the best but it works and there is no need to do anything manually. I came up with this solution based on the following Vladislav Martin's explained fact: "The y-coordinates in this array only serve to distinguish Array1, Array2, and so on from each other".
My original arrays are:
Array4=[Nan....;20;21;22;23;24;..........60]
Array3=[Nan....;20;21;22;23;24;..........60]
Array2=[0;1;2;3;4;5;6;Nan;Nan;Nan;Nan;17;18;.....60]
Array1=[0;1;2;3;4;5;6;Nan;Nan;Nan;Nan;17;18;.....60]
x={'0m';'1m';'2m';'3m';'4m';....'60m'}
The values contained in these arrays make reference to the x-axis on the graph. In order to make the things more simple and to avoid having to code a function to determine the length for each discontinuity in the arrays, I replace these values for y-axis position values. Basically I give to Array1 y-axis position values of 0 and to Array2 0+0.02=0.02. To Array3 I give y-axis position values of 0.5 and to Array4 0.5+0.02=0.52. In this way, Array2 will be plotted on the graph closer to Array1 which will form the first group and Array4 closer to Array3 which will form the second group.
Datatable=table(Array1,Array2,Array3,Array4);
cont1=0;
cont2=0.02;
for col=1:2:size(Datatable,2)
col2=col+1;
for row=1:size(Datatable,1)
if isnan(Datatable{row,col})==0 % For first array in the group: If the value is not nan, I replace it for the corresponnding cont1 value
Datatable{row,col}=cont1;
end
if isnan(Datatable{row,col2})==0 % For second array in the group: If the value is not nan, I replace it for the corresponnding cont2 value
Datatable{row,col2}=cont2;
end
end
cont1=cont1+0.5;
cont2=cont2+0.5;
end
The result of the above code will be a table like the following:
And now I plot the Arrays using 2D floating lines:
figure
for array=1:2:size(Datatable,2)
FirstPair=cell2mat(table2cell(Datatable(:,array)));
SecondPair=cell2mat(table2cell(Datatable(:,array+1)));
hold on
plot(1:numel(x),FirstPair,'r','Linewidth',6)
plot(1:numel(x),SecondPair,'b','Linewidth',6)
hold off
end
set(gca,'xticklabel',x)
And this will generate the following graph:

Trouble zooming with axis crossing at 0 in Oxyplot

[Edit: I submitted an issue about this on the OxyPlot GitHub]
I have an OxyPlot cartesian graph in a WPF Window with some FunctionSeries. When I set PositionAtZeroCrossing at true for both the axes, several problems appear :
1) The titles are not displayed correctly, one doesn't even appear. Changing the value of TitlePosition does not seem to change anything.
2) When zooming in or moving around, the x axis goes outside the graph area, as shown below :
Both problems do not appear when PositionAtZeroCrossing is not set as true.
I am wondering if there is a correct way to fix those problems, or a workaround (I am not familiar with OxyPlot).
//My PlotModel is binded to the Model of a PlotView in my WPF control.
//FonctionQlimPlim, maxX, maxY, minX and maxY are defined elsewhere
PlotModel plot = new PlotModel()
{
PlotType = PlotType.Cartesian
};
plot.Axes.Add(new LinearAxis()
{
Position = AxisPosition.Bottom,
Minimum = minX,
Maximum = maxX,
Title = "Q (kVAR)",
PositionAtZeroCrossing = true
});
plot.Axes.Add(new LinearAxis()
{
Position = AxisPosition.Left,
Minimum = minY,
Maximum = maxY,
Title = "P (kW)",
PositionAtZeroCrossing = true
});
//One of the FunctionSeries
var f = FonctionQlimPlim;
f.Color = OxyColors.Red;
plot.Series.Add(f);
It seems like the usage for this property is something along the following:
Let's assume your input is an x (horizontal),y (vertical) graph.
Let's say your x values go from 1 to 20, and let's assume that your y values will be a random number between -10 and 10.
So, we're expecting to see a graph with a 20point jumping up and down randomly.
When you set the axis PositionAtZeroCrossing, you're telling Oxyplot to put it where the crossing is at. Here's some screenshots to help you out: Link to bigger image.
So, depending on where your zero is, it might be far far outside of your viewable screen, and hence it seems to not be there.
To be honest, I don't see the point in setting them bot to true, but it might suit some needs I guess.
It was a bug, traced here, that was fixed in October 2014.

Plotting from a 2D Array Using a Loop

This seems like a trivial problem, though I've been hitting myself over the head with it for too long.
This doesn't even plot just the (0,0) -- I can't seem to find much about plotting from arrays -- rather just matrix plots (and only columns at that).
The data is properly in these arrays, I just need to make plots! Doesn't seem so complicated. I don't even need separate colors for the different sets...just all one big scatter plot.
Any suggestions?
pdf(mypath)
# Plot first point
plot(0,0, col = "blue", type = "n", xlab="PES", ylab=""%eff")
#Loop to Plot remaining points
for(rows in 1:nrowX)
{
for(cols in 1:ncolX)
{
points(X[rows,cols],Y[rows,cols], col = "blue", type = "p")
}
}
dev.off
I have also tried using plot.new() to have an empty plot...but no such luck.
SOLUTION!!
Turns out I'm just a fool. Code is acurate and the suggestions below do indeed work.
R happened to be open in another tab and since it was open, never let go of the plot (why? I don't know). As soon as it was closed, the plot appeared. Now I can get my plot again and again...
Thanks to everyone who tried helping a problem that wasn't a problem!
I like this place already!
When you set type = "n", the plot function will not plot anything at all. It is used to set up a basis for the rest of the plot (like axis labels, limits etc). That is why the first point at (0, 0) does not show up.
The rest of the points are probably outside the range. Use xlim and ylim to set up the ranges properly. I'm going to assume X and Y have the same size and dimension. Try this:
pdf(mypath)
# Set up the plot
plot(0, type="n", xlab="PES", ylab="%eff", xlim=range(X), ylim=range(y))
# Now plot
points(X,Y, col="blue")
dev.off
Of course you could let the plot function take care of the limits for you:
pdf(mypath)
plot(X, Y, xlab="PES", ylab="%eff")
dev.off()
Your initial plot will set up the coordinates, but since you only give it one point it does not know how much room to leave around the 0,0 point (so it does not leave very much). I expect that the rest of your points fall outside of that range which is why they don't show up on the plot (you can use par("usr") to see what the extents are).
When you create the initial plot you should include xlim and ylim arguments so that the plot includes the area where the new points will be added, something like:
plot(0,0, type='n', xlim=range(X), ylim=range(Y))
You may also be interested in the matplot function which will take a matrix as either or both the x and/or y argument and plot accordingly.
Edit
The following works for me:
X <- matrix( runif(390), nrow=10 )
Y <- matrix( rnorm(390), nrow=10 )
plot(0,0, col = "blue", type = "n", xlab="PES", ylab="%eff",
xlim=range(X), ylim=range(Y))
#Loop to Plot remaining points
for(rows in 1:nrow(X))
{
for(cols in 1:ncol(X))
{
points(X[rows,cols],Y[rows,cols], col = "blue", type = "p")
}
}
I did remove an extra " from the ylab, was that your problem?
But
plot(X,Y)
also worked without the looping.
Check with just the console to see if it works before worrying about sending to a pdf file. If this has not fixed it yet, we still need more details.

Why am I getting weird vertex position values in MaxScript

I am trying to write a simple mesh exporter in maxscript. It's nothing fancy, it just has to export faces, vertices and tvertices. I have the code as good as working, but sometimes I get really weird values in vertex positions (-1.1234e-005 for example). I understand it is some kind of really big number, but the problem is, my verts aren't anywhere near the position that number indicates (I have seen this happen with a 1m*1m*1m box). I have found that when it happens with a mesh, it always happens with that mesh and with the same vertex, untill I move that specific vertex (scaling/moving the whole thing doesn't work). I use this code to export the vertex positions:
num_verts = sel_mesh.numverts
for i=1 to num_verts do (
v = getVert sel_mesh i
format "v %\n" v to:out_file
)
format "\n" to:out_file
I have tried Googling the problem, but no one seems to have the same issue. I use the same code for my tvertices and those are exported perfectly fine. I can post the whole exporter if neccesary. Please let me know if you need to see more code :).
This is infact a very small number.
-1.1234e-005 is -1.1234 * (10 ^ -5), which is very small.
Contrary to your comment, formattedPrint does 'fix' this.
formattedPrint -1.1234e-005 format:".6f"
output: "-0.000011"
You can use it as such in your exporter:
num_verts = sel_mesh.numverts
for i=1 to num_verts do (
v = getVert sel_mesh i
format "v %\n" (formattedPrint v format:".6f") to:out_file
)
format "\n" to:out_file

Resources