iOS: I can't get Core Text to use word wrapping. Where am I going wrong? - core-text

I've an attributed string with multiple words. I'm trying to draw it along a CGPathRef using Core Text on iOS7. However, even when I set the CTLineBreakMode to kCTLineBreakByWordWrapping, the text does not wrap on words, but on characters. For instance, the red, green, blue and purple pie slices all have enough room to display the string using word wrapping, yet Core Text insists on wrapping on characters in those cases. What am I doing wrong?
UIFont *font = [UIFont boldSystemFontOfSize:10];
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColorWithColor(context, [UIColor clearColor].CGColor);
CGMutablePathRef pathSmaller = CGPathCreateMutable();
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGPathMoveToPoint(pathSmaller, NULL, self.center.x, self.center.y);
CGPathAddArc(pathSmaller, NULL, self.center.x, self.center.y, radius - 5, (beginAngle - 90 - 1) * M_PI / 180, (endAngle - 90 - 1) * M_PI / 180, NO);
CGPathCloseSubpath(pathSmaller);
CGContextAddPath(context, pathSmaller);
CGContextFillPath(context);
CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;
CTParagraphStyleSetting settings[] = {
{ kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode },
};
CTParagraphStyleRef pstyle = CTParagraphStyleCreate(settings, 1);
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:#"Mortgage Insurance"];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attrString, CFRangeMake(0, [attrString length]), kCTParagraphStyleAttributeName, pstyle);
CFRelease(pstyle);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CTFrameRef theFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attrString length]), pathSmaller, NULL);
CFRelease(framesetter);
CTFrameDraw(theFrame, context);
CFRelease(theFrame);

Related

GlyphRun and the exact position of the cursor (WPF)

I would like to draw a text exactly in the place of the mouse cursor.
Because I need very high performance, I would like to use GlyphRun.
Everything works almost well, but unfortunately my text is slightly below the cursor.
Can someone help me modify this method to eliminate this vertical shift?
Now it looks like this
My expectation (the text touches the cursor)
My code:
void MyDrawer_MouseMove(object sender, MouseEventArgs e)
{
Test1();
}
void Test1()
{
MyDrawer.DeleteVisual(Dv);
MyDrawer.Cursor = Cursors.Cross;
string text = "Hello Word";
double size = 40;
Dv = new DrawingVisual();
using (var dc = Dv.RenderOpen())
{
Typeface typeface = new Typeface("Arial");
if (typeface.TryGetGlyphTypeface(out GlyphTypeface glyphTypeface))
{
ushort[] glyphIndexes = new ushort[text.Length];
double[] advanceWidths = new double[text.Length];
for (int i = 0; i < text.Length; i++)
{
ushort glyphIndex = glyphTypeface.CharacterToGlyphMap[text[i]];
glyphIndexes[i] = glyphIndex;
double width = glyphTypeface.AdvanceWidths[glyphIndex] * size;
advanceWidths[i] = width;
}
Point origin = Mouse.GetPosition(MyDrawer);
//Move text belowe the cursor
origin = new Point { X = origin.X, Y = origin.Y + (glyphTypeface.Baseline * size) };
GlyphRun glyphRun = new GlyphRun(glyphTypeface, 0, false, size,
glyphIndexes, origin, advanceWidths, null, null, null, null,
null, null);
dc.DrawGlyphRun(Brushes.Red, glyphRun);
MyDrawer.AddVisual(Dv);
}
}
}
Of course, this is only a test, in practice it will not affect the cursor, but the indicated point and text will be much more than in this example.
I bumped into this question in search for the same issue.
Glyph content (GlyphRun for that matter) is wrapped in a black-box.
The black-box contain a bit of padding on the top.
Like so : Rought skecth of the glyph run
// To get the box size
var blackBoxHeight = glyphTypeface.Height * fontSize;
// To get the actual character height
var characterHeight = glyphTypeface.Baseline * fontSize;
// To get the padding inside the black box
var blackBoxPadding = blackBoxHeight - characterHeight;
// set the point to draw a little bit up (skip the padding)
origin.Y -= blackBoxPadding;
// Create the glyph
var run = new GlyphRun
(
glyphTypeface: glyphTypeface,
bidiLevel: 0,
isSideways: false,
renderingEmSize: fontSize,
pixelsPerDip: 1.0f,
glyphIndices: indices,
baselineOrigin: origin, /* The point containing the padding offset */
advanceWidths: widths,
glyphOffsets: null,
characters: null,
deviceFontName: null,
clusterMap: null,
caretStops: null,
language: null
);
The other answer lacks of some details.
There is actually no magic or unreasoned paddings. The picture shows relations of glyphTypeface height parameters to the displayed GlyphRun.
The code that calculates corresponding line vertical positions based on the GlyphRun origin Y is shown below:
var baseLine = origin.Y;
var boxTop = baseLine - glyphTypeface.Baseline * FontSize;
var boxBottom = boxTop + glyphTypeface.Height * FontSize;
var capsHeight = baseLine - glyphTypeface.CapsHeight * FontSize;
var lowHeight = baseLine - glyphTypeface.XHeight * FontSize;

why is metal shader gradient lighter as a SCNProgram applied to a SceneKit Node than it is as a MTKView?

I have a gradient, generated by a Metal fragment shader that I've applied to a SCNNode defined by a plane geometry.
It looks like this:
When I use the same shader applied to a MTKView rendered in an Xcode playground, the colors are darker. What is causing the colors to be lighter in the Scenekit version?
Here is the Metal shader and the GameViewController.
Shader:
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
struct myPlaneNodeBuffer {
float4x4 modelTransform;
float4x4 modelViewTransform;
float4x4 normalTransform;
float4x4 modelViewProjectionTransform;
float2x3 boundingBox;
};
typedef struct {
float3 position [[ attribute(SCNVertexSemanticPosition) ]];
float2 texCoords [[ attribute(SCNVertexSemanticTexcoord0) ]];
} VertexInput;
struct SimpleVertexWithUV
{
float4 position [[position]];
float2 uv;
};
vertex SimpleVertexWithUV gradientVertex(VertexInput in [[ stage_in ]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
SimpleVertexWithUV vert;
vert.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
int width = abs(scn_node.boundingBox[0].x) + abs(scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y) + abs(scn_node.boundingBox[1].y);
float2 resolution = float2(width,height);
vert.uv = vert.position.xy * 0.5 / resolution;
vert.uv = 0.5 - vert.uv;
return vert;
}
fragment float4 gradientFragment(SimpleVertexWithUV in [[stage_in]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
float4 fragColor;
float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1-in.uv.y));
fragColor = float4(color,1);
return(fragColor);
}
Game view controller:
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
#IBOutlet weak var gameView: GameView!
override func awakeFromNib(){
super.awakeFromNib()
// create a new scene
let scene = SCNScene()
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// turn off default lighting
self.gameView!.autoenablesDefaultLighting = false
// set the scene to the view
self.gameView!.scene = scene
// allows the user to manipulate the camera
self.gameView!.allowsCameraControl = true
// show statistics such as fps and timing information
self.gameView!.showsStatistics = true
// configure the view
self.gameView!.backgroundColor = NSColor.black
var geometry:SCNGeometry
geometry = SCNPlane(width:10, height:10)
let geometryNode = SCNNode(geometry: geometry)
let program = SCNProgram()
program.fragmentFunctionName = "gradientFragment"
program.vertexFunctionName = "gradientVertex"
let gradientMaterial = SCNMaterial()
gradientMaterial.program = program
geometry.materials = [gradientMaterial]
scene.rootNode.addChildNode(geometryNode)
}
}
As explained in the Advances in SceneKit Rendering session from WWDC 2016, SceneKit now defaults to rendering in linear space which is required to have accurate results from lighting equations.
The difference you see comes from the fact that in the MetalKit case you are providing color components (red, green and blue values) in the sRGB color space, while in the SceneKit case you are providing the exact same components in the linear sRGB color space.
It's up to you to decide which result is the one you want. Either you want a gradient in linear space (that's what you want if you are interpolating some data) or in gamma space (that's what drawing apps use).
If you want a gradient in gamma space, you'll need to convert the color components to be linear because that's what SceneKit works with. Taking the conversion formulas from the Metal Shading Language Specification, here's a solution:
static float srgbToLinear(float c) {
if (c <= 0.04045)
return c / 12.92;
else
return powr((c + 0.055) / 1.055, 2.4);
}
fragment float4 gradientFragment(SimpleVertexWithUV in [[stage_in]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1 - in.uv.y));
color.r = srgbToLinear(color.r);
color.g = srgbToLinear(color.g);
color.b = srgbToLinear(color.b);
float4 fragColor = float4(color, 1);
return(fragColor);
}
After learning the root cause of this problem, I did a bit more research on the topic and found another solution. Gamma space rendering can be forced application wide by setting
SCNDisableLinearSpaceRendering to TRUE in the application's plist.
I'm not sure, but it looks to me like your calculation of the size of the node is off, leading your .uv to be off, depending on the position of the node.
You have:
int width = abs(scn_node.boundingBox[0].x) + abs(scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y) + abs(scn_node.boundingBox[1].y);
I would think that should be:
int width = abs(scn_node.boundingBox[0].x - scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y - scn_node.boundingBox[1].y);
You want the absolute difference between the two extremes, not the sum. The sum gets larger as the node moves right and down, because it effectively includes the position.
All of that said, isn't the desired (u, v) already provided to you in in.texCoords?

How to set width of line in 3D

Am using SharpDX on DirectX 9 to draw a line.
Line is drawn correctly with the following code.
Vector3 startPoint= ...
Vector3 endPoint = ...
Vector3[] data = new Vector3[] { startPoint, endPoint };
device.DrawUserPrimitives<Vector3>(PrimitiveType.LineList, 1, data);
Question 1: How do we set the width of line?
Question 2: If a graphics card is used, then line width is less.
if there is no graphics card, a thicker line has been drawn.
Answer to Question 1
Using Line class, we can set the width of line.
Matrix worldViewProjection = worldMatrix * viewMatrix * projectionMatrix;
Line line = new Line(device);
line.Width = 2;
ColorBGRA lineColor = new ColorBGRA(255, 0, 0, 255);
line.Begin();
line.DrawTransform(new Vector3[] { anchorPoint, cursorPoint }, worldViewProjection, lineColor);
line.End();
Question 2 is still unanswered.

FreeType: sizing fonts based on cap height?

I am rendering text, and I need the font to have the cap height to a certain number of pixels. For example, in the sign below, I need to have the words SPEED and LIMIT to be the same height, in this case 45 px, so I set the font size to 45 (which I now understand wasn't such a good idea) and get bounding boxes (in red) different from what I should have had.
So I create the font object,
Text *t = (Text *)e;
cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
cairo_t *cr = cairo_create(s);
cairo_font_face_t *font = cairo_ft_font_face_create_for_ft_face(fonts[t->series], 0);
cairo_set_font_face(cr, font);
Set the font size to 45,
cairo_set_font_size(cr, 1.5 * t->size);
Then calculate the extents of the bounding box, which doesn't line up with the correct size of the text.
cairo_text_extents_t ext;
cairo_text_extents(cr, t->data, &ext);
t->geom->height = ext.height; // t->size;
t->geom->width = ext.width;
t->lsb = ext.x_bearing;
cairo_font_face_destroy(font);
cairo_destroy(cr);
cairo_surface_destroy(s);
I'm guessing I have to use FT_Set_Pixel_Sizes or something, but I don't exactly know how to use that.
EDIT: Is there a way to set the cap height using Cairo? If so, I'd rather use that.
Using #Jongware's comment, I added a scale field which calculates the scaling factor for this particular piece of text (it should be consistent for a certain cap height).
cairo_text_extents_t ext, xext;
cairo_text_extents(cr, t->data, &ext);
cairo_text_extents(cr, "X", &xext);
t->scale = t->size / xext.height;
t->geom->height = t->size;
t->geom->width = ext.width * t->scale;
t->lsb = ext.x_bearing * t->scale;
It works perfectly now.

WPF - Modifying Image Colors on the Fly (C#)

Is it possible to modify the colors of an Image in WPF via code (or even using Templates)?
Suppose I have an image which I need to apply to a Tile - which will have a White Foreground color by default and a Transparent Background. Something like the following PNG (it is somewhere here!):
Instead of adding different images - with different colors, I just want to manipulate the White - and say change it to Black.
If it can be done, can someone give me a few pointers on what I need to do/look into.
One way to do this would be to use the BitmapDecoder class to retrieve the raw pixel data. You can then modify the pixels, and build a new WriteableBitmap from that modified pixel data:
// Copy pixel colour values from existing image.
// (This loads them from an embedded resource. BitmapDecoder can work with any Stream, though.)
StreamResourceInfo x = Application.GetResourceStream(new Uri(BaseUriHelper.GetBaseUri(this), "Image.png"));
BitmapDecoder dec = BitmapDecoder.Create(x.Stream, BitmapCreateOptions.None, BitmapCacheOption.Default);
BitmapFrame image = dec.Frames[0];
byte[] pixels = new byte[image.PixelWidth * image.PixelHeight * 4];
image.CopyPixels(pixels, image.PixelWidth*4, 0);
// Modify the white pixels
for (int i = 0; i < pixels.Length/4; ++i)
{
byte b = pixels[i * 4];
byte g = pixels[i * 4 + 1];
byte r = pixels[i * 4 + 2];
byte a = pixels[i * 4 + 3];
if (r == 255 &&
g == 255 &&
b == 255 &&
a == 255)
{
// Change it to red.
g = 0;
b = 0;
pixels[i * 4 + 1] = g;
pixels[i * 4] = b;
}
}
// Write the modified pixels into a new bitmap and use that as the source of an Image
var bmp = new WriteableBitmap(image.PixelWidth, image.PixelHeight, image.DpiX, image.DpiY, PixelFormats.Pbgra32, null);
bmp.WritePixels(new Int32Rect(0, 0, image.PixelWidth, image.PixelHeight), pixels, image.PixelWidth*4, 0);
img.Source = bmp;
This works after a fashion, but there's a problem. Here's how the result looks if I show it on a dark background:
As you can see, it's got a sort of white border. What's happened here is that your white cross had anti-aliased edges, meaning that the pixels around the edges are actually a semi-transparent shade of grey.
We can deal with that using a slightly more sophisticated technique in the pixel modification loop:
if ((r == 255 &&
g == 255 &&
b == 255 &&
a == 255) ||
(a != 0 && a != 255 &&
r == g && g == b && r != 0))
{
// Change it to red.
g = 0;
b = 0;
pixels[i * 4 + 1] = g;
pixels[i * 4] = b;
}
Here's how that looks on a black background:
As you can see, that looks right. (OK, you wanted black not red, but the basic approach will be the same for any target colour.)
EDIT 2015/1/21 As ar_j pointed out in the comments, the Prgba format requires premultiplication. For the example I've given it is actually safe to ignore it, but if you were modifying colour channels in any way other than by setting them to 0, you'd need to multiple each value by (a/255). E.g., as aj_j shows for the G channel: pixels[i * 4 + 1] = (byte)(g * a / 255); Since g is zero in my code, this makes no difference but for non-primary colours you would need to do it that way.
Here it is on a gradient fill background just to show that the transparency is working:
You could also write out the modified version:
var enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmp));
using (Stream pngStream = File.OpenWrite(#"c:\temp\modified.png"))
{
enc.Save(pngStream);
}
Here's the result:
You can see the red cross, and it'll be on top of whatever background colour StackOverflow is using. (White, as I write this, but maybe they'll redesign one day.)
Whether this will work for the images you want to use is harder to know for certain, because it depends on what your definition of 'white' is - depending on how your images were produced, you may find things are ever so slightly off-white (particularly around the edges), and you may need further tweaking. But the basic approach should be OK.

Resources