SKIA - Inaccurate value returned by measureText() - skia

I have a problem measuring text using skia measureText() function.
The value returned is inaccurate.
SkPaint *skPaint = new SkPaint();
SkTypeface* myFont = SkTypeface::CreateFromName("Impact", SkTypeface::kNormal);
skPaint->setTypeface(myFont);
skPaint->setAntiAlias(true);
skPaint->setTextAlign(SkPaint::kLeft_Align);
skPaint->setTextEncoding(SkPaint::kUTF16_TextEncoding);
skPaint->setTextSize(SkIntToScalar(120));
skPaint->setColor(0xff000001);
canvas->drawText(text, length, SkIntToScalar(x) , SkIntToScalar(y) , *skPaint);
SkScalar width = skPaint->measureText(text, length);
The width returned by measureText() is 451.
I checked the generated bitmap text via a photo editor app, the actual width is only 438.
Any thoughts on getting the accurate width of text in SKIA?
Thank you!

I believe what you are trying to match will come from "bounds"
SkRect bounds;
SkScalar textWidth = paint.measureText("some", 4, &bounds);
which is a minimum rectangle to fit a given text, whereas textWidth is slightly larger than that.

I faced this issue too. Dont know why exactly it happens, maybe because of kerning differences, but i came to this:
SizeF RenderTextAndroid::GetStringSizeF() {
UpdateFont();
const base::string16& text = GetLayoutText();
std::vector<SkScalar> widths(text.length());
paint_.getTextWidths(text.c_str(), GetStrByteLen(text), &widths[0], NULL);
return SizeF(std::accumulate(widths.begin(), widths.end(), 0),
font_metrics_.fBottom - font_metrics_.fTop);
}
Where UpdateFont just sets new parameters to SkPaint

Related

Visio Shape Text positioning below the shape using Text Transform

I have a building a set of stencil shapes and I need the text to display below the shape. I am using custom formulas to generate the text, and as such the volume of text changes from use case to use case.
What I have come across is using the Text Transform set of properties, and I have tried the following with success for a single line of text:
TxtWidth = TEXTWIDTH(TheText)
TxtPinX = Width * 0.5
TxtLocPinX = TxtWidth * 0.5
TxtHeight = Height * 0
TxtPinY = Height * -0.2
TxtLocPinY = TxtHieght * 0.5
TxtAngle = 0 deg
The problem arises when there is more than a single line of text to display -> the text appears 'half above (inside) and half below' the bottom the shape.
I would like to place all the text, regardless of how many lines there are, underneath the shape.
What I have tried is to set the TxtPinY = some formula different from above eg/ Height * -(TxtHeight). This seems to always result in an 'error in formula'.
I am sure that this is something simple that I am missing, but I cannot figure it out.
Can anybody point me in the right direction?
Cheers and thanks for taking a look at this,
The Frog
You could try the TEXTHEIGHT function to get around this. Specify a reasonable maximum text widht as a second parameter for it:
TxtHeight = TEXTHEIGHT(TheText,100)
TxtPinY = 0
TxtLocPinY = TxtHeight
You can use the code provided with the stencil available in this post:
http://visguy.com/vgforum/index.php?topic=7461.msg31490#msg31490

How to find bit length of text with specific font and font size

I'm developing NativeScript JavaScript code to create dynamic text marker for maps. I have the code working that creates a marker for a specific string. My next step is to take any given string, determine its height and width in bits, and create the marker sized to contain the text.
My problem is finding the size of the text, given the text string itself, the font size, and the font family.
It looks like getMeasuredWidth could work, except that the string must already be loaded on a page before that function will return a value. In my case, I simply need to compute the size; the text won't otherwise appear as such on a page (the text in the marker becomes an image).
Is there a way to do this?
var bmp = BitmapFactory.create(200);
bmp.dispose(function (b) {
try {
b.drawRect(
"100,34", // size
'0,0', // upper-left coordinate
KnownColors.Black, // border color
KnownColors.Cornsilk // fill color
);
b.writeText(
"Parking",
"2,25",
{ color: KnownColors.Black, size: 8, name: 'fontawesome-webfont', });
...
In the code above, the width of "100" of the bounding rectangle actually represents the bit width of "Parking" with a small amount of padding. What I want to does calculate the rectangle's height and width and not hard-code it.
Try this, finding label size without adding it to Page upon button click
export function onFindButtonTap(args: EventData) {
const button = <any>args.object;
const label = new Label();
label.text = "Hello, found my size?"
label.fontSize = 20;
(<any>label)._setupAsRootView(button._context);
label.onLoaded();
label.measure(0, 0);
console.log(`Width : ${label.getMeasuredWidth()} x Height : ${label.getMeasuredHeight()}`);
}
Playground Sample
Note: I didn't get a chance to test it with iOS yet, let me know if you hit any issues.

Extendscript: How to check whether text content overflows the containing rectangle

I am using Extendscript for Photoshop CS5 to change the text of a text layer. Is there a way of checking whether the text fits e.g. by checking whether it overflows after changing the content?
I created a solution that works perfectly fine :). Maybe someone else can use it as well. Let me know if it works for you too!
function scaleTextToFitBox(textLayer) {
var fitInsideBoxDimensions = getLayerDimensions(textLayer);
while(fitInsideBoxDimensions.height < getRealTextLayerDimensions(textLayer).height) {
var fontSize = parseInt(textLayer.textItem.size);
textLayer.textItem.size = new UnitValue(fontSize * 0.95, "px");
}
}
function getRealTextLayerDimensions(textLayer) {
var textLayerCopy = textLayer.duplicate(activeDocument, ElementPlacement.INSIDE);
textLayerCopy.textItem.height = activeDocument.height;
textLayerCopy.rasterize(RasterizeType.TEXTCONTENTS);
var dimensions = getLayerDimensions(textLayerCopy);
textLayerCopy.remove();
return dimensions;
}
function getLayerDimensions(layer) {
return {
width: layer.bounds[2] - layer.bounds[0],
height: layer.bounds[3] - layer.bounds[1]
};
}
How to use / Explanation
Create a text layer that has a defined width and height.
You can change the text layers contents and then call scaleTextToFitBox(textLayer);
The function will change the text/font size until the text fits inside the box (so that no text is invisible)!
The script decreases the font size by 5% (* 0.95) each step until the texts fits inside the box. You can change the multiplier to achieve a more precise result or to increase performance.
I haven't found a way to do this directly. But I've used the following technique to determine the height I needed for a textbox (I wanted to keep the width constant) before.
expand the textbox's height well beyond what is needed to accommodate the text inside it.
duplicate the layer
rasterize the duplicate
measure the bounds of the rasterized layer.
adjust the bounds of the original text layer as needed
delete the rasterized duplicate
Totally roundabout - but it did work.

Resize CATextLayer to fit text on iOS

All my research so far seems to indicate it is not possible to do this accurately. The only two options available to me at the outset were:
a) Using a Layout manager for the CATextLayer - not available on iOS as of 4.0
b) Use sizeWithFont:constrainedToSize:lineBreakMode: and adjust the frame of the CATextLayer according to the size returned here.
Option (b), being the simplest approach, should work. After all, it works perfectly with UILabels. But when I applied the same frame calculation to CATextLayer, the frame was always turning out to be a bit bigger than expected or needed.
As it turns out, the line-spacing in CATextLayers and UILabels (for the same font and size) is different. As a result, sizeWithFont (whose line-spacing calculations would match with that of UILabels) does not return the expected size for CATextLayers.
This is further proven by printing the same text using a UILabel, as against a CATextLayer and comparing the results. The text in the first line overlaps perfectly (it being the same font), but the line-spacing in CATextLayer is just a little shorter than in UILabel. (Sorry I can't upload a screenshot right now as the ones I already have contain confidential data, and I presently don't have the time to make a sample project to get clean screenshots. I'll upload them later for posterity, when I have the time)
This is a weird difference, but I thought it would be possible to adjust the spacing in the CATextLayer by specifying the appropriate attribute for the NSAttributedString I use there, but that does not seem to be the case. Looking into CFStringAttributes.h I can't find a single attribute that could be related to line-spacing.
Bottomline:
So it seems like it's not possible to use CATextLayer on iOS in a scenario where the layer is required to fit to its text. Am I right on this or am I missing something?
P.S:
The reason I wanted to use CATextLayer and NSAttributedString's is because the string to be displayed is to be colored differently at different points. I guess I'd just have to go back to drawing the strings by hand as always....of course there's always the option of hacking the results from sizeWithFont to get the proper line-height.
Abusing the 'code' tags a little to make the post more readable.
I'm not able to tag the post with 'CATextLayer' - surprisingly no such tags exist at the moment. If someone with enough reputation bumps into this post, please tag it accordingly.
Try this:
- (CGFloat)boundingHeightForWidth:(CGFloat)inWidth withAttributedString:(NSAttributedString *)attributedString {
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString( (CFMutableAttributedStringRef) attributedString);
CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(inWidth, CGFLOAT_MAX), NULL);
CFRelease(framesetter);
return suggestedSize.height;
}
You'll have to convert your NSString to NSAttributedString. In-case of CATextLayer, you can use following CATextLayer subclass method:
- (NSAttributedString *)attributedString {
// If string is an attributed string
if ([self.string isKindOfClass:[NSAttributedString class]]) {
return self.string;
}
// Collect required parameters, and construct an attributed string
NSString *string = self.string;
CGColorRef color = self.foregroundColor;
CTFontRef theFont = self.font;
CTTextAlignment alignment;
if ([self.alignmentMode isEqualToString:kCAAlignmentLeft]) {
alignment = kCTLeftTextAlignment;
} else if ([self.alignmentMode isEqualToString:kCAAlignmentRight]) {
alignment = kCTRightTextAlignment;
} else if ([self.alignmentMode isEqualToString:kCAAlignmentCenter]) {
alignment = kCTCenterTextAlignment;
} else if ([self.alignmentMode isEqualToString:kCAAlignmentJustified]) {
alignment = kCTJustifiedTextAlignment;
} else if ([self.alignmentMode isEqualToString:kCAAlignmentNatural]) {
alignment = kCTNaturalTextAlignment;
}
// Process the information to get an attributed string
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
if (string != nil)
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), (CFStringRef)string);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTForegroundColorAttributeName, color);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTFontAttributeName, theFont);
CTParagraphStyleSetting settings[] = {kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, sizeof(settings) / sizeof(settings[0]));
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTParagraphStyleAttributeName, paragraphStyle);
CFRelease(paragraphStyle);
NSMutableAttributedString *ret = (NSMutableAttributedString *)attrString;
return [ret autorelease];
}
HTH.
I have a much easier solution, that may or may not work for you.
If you aren't doing anything special with the CATextLayer that you can't do a UILabel, instead make a CALayer and add the layer of the UILabel to the CALayer
UILabel*label = [[UILabel alloc]init];
//Do Stuff to label
CALayer *layer = [CALayer layer];
//Set Size/Position
[layer addSublayer:label.layer];
//Do more stuff to layer
With LabelKit you don't need CATextLayer anymore. No more wrong line spacing and wider characters, all is drawn in the same way as UILabel does, while still animated.
This page gave me enough to create a simple centered horizontally CATextLayer : http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html
- (void)drawInContext:(CGContextRef)ctx {
CGFloat height, fontSize;
height = self.bounds.size.height;
fontSize = self.fontSize;
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, 0.0, (fontSize-height)/2.0 * -1.0);
[super drawInContext:ctx];
CGContextRestoreGState(ctx);
}

Error in Pyramid mean shift filtering of image of certain dimensions?

I'm trying to run the mean shift segmentation using pyramids as explained in the Learning OpenCV book on some images. Both source and destination images are 8-bit, three-channel color images of the same width and height as mentioned.
However correct output is obtained only on a 1600x1200 or 1024x768 images. Other images of sizes 625x391 and 644x438 are causing a runtime error
"Sizes of input arguments do not match in function cvPyrUp()"
My code is this:
IplImage *filtered = cvCreateImage(cvGetSize(img),img->depth,img->nChannels);
cvPyrMeanShiftFiltering( img, filtered, 20, 40, 1);
The program uses the parameters as given in the sample. I've tried decreasing values thinking it to be an image dimensions problem, but no luck.
By resizing image dimensions to 644x392 and 640x320 the mean-shift is running properly. I've read that "pyramid segmentation requires images that are N-times divisible by 2, where N is the number of pyramid layers to be computed" but how is that applicable here?
Any suggestions please.
Well you have anything wring except that when you apply cvPyrMeanShiftFiltering
you should do it like this:
//A suggestion to avoid the runtime error
IplImage *filtered = cvCreateImage(cvGetSize(img),img->depth,img->nChannels);
cvCopy(img,filtered,NULL);
//Values only you should know
int level = kLevel;
int spatial_radius = kSpatial_Radius;
int color_radius = = kColor_Radius;
//Here comes the thing
filtered->width &= -(1<<level);
filtered->height &= -(1<<level);
//Now you are free to do your thing
cvPyrMeanSihftFiltering(filtered, filtered,spatial_radius,color_radius,level);
The thing is that this kind of pyramidal filter modifies som things acording the level you use. Try this and tell me later if worked.
Hope i can help.

Resources