What is the difference between alphabetic and ideographic in Flutter's TextBaseline enum - text

The TextBaseline enum in Flutter has two options:
alphabetic
ideographic
How do these values actually change the baseline?

TextBaseline.alphabetic
The alphabetic baseline is the line that the letters in alphabets like English sit on. Here is an example:
You can see that the English letters sit nicely on the line, but it cuts through the Chinese characters.
TextBaseline.ideographic
When you use the ideographic option, though, the baseline is at the bottom of the text area. Note that the Chinese characters don't actually sit right on the line. Rather, the line is at the very bottom of the text line.
Supplemental code
You can plug this into a CustomPaint widget (as described here) to reproduce the above examples.
#override
void paint(Canvas canvas, Size size) {
final textStyle = TextStyle(
color: Colors.black,
fontSize: 30,
);
final textSpan = TextSpan(
text: 'My text 文字',
style: textStyle,
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout(
minWidth: 0,
maxWidth: size.width,
);
print('width: ${textPainter.width}');
print('height: ${textPainter.height}');
// draw a rectangle around the text
final left = 0.0;
final top = 0.0;
final right = textPainter.width;
final bottom = textPainter.height;
final rect = Rect.fromLTRB(left, top, right, bottom);
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 1;
canvas.drawRect(rect, paint);
// draw the baseline
final distanceToBaseline =
textPainter.computeDistanceToActualBaseline(TextBaseline.ideographic);
print('distanceToBaseline: ${distanceToBaseline}');
canvas.drawLine(
Offset(0, distanceToBaseline),
Offset(textPainter.width, distanceToBaseline),
paint,
);
// draw the text
final offset = Offset(0, 0);
textPainter.paint(canvas, offset);
}

Related

Android compose: how to position icons in the margins of a text in a row?

I would like to have a card with the following layout:
an icon on the left;
text in the center;
an icon to the right;
The icons must always be present regardless of the length of the text:
In this regard I wrote the following code:
fun test() {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp)
) {
Row(
Modifier.fillMaxWidth().padding(all = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
Text("Title", textAlign = TextAlign.Center)
Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete")
}
}
}
The problem is that if the text is too long, then the last icon "disappears":
A solution could be to use Modifier.width (x.dp) on the text, but in this case how do I set the value of x to cover the maximum possible width within the icons?
You can apply Modifier.weight(1f) to the Text composable.
Something like:
Row(
Modifier
.fillMaxWidth()
.height(30.dp)
){
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
Text("Title", textAlign = TextAlign.Center,
modifier = Modifier.weight(1f)) // Fill this with remaining space available
Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete")
}
You can make this spacing arrangement with distributed weights directly.
I've added each icon weight 1 and rest of the space to title.
Or you may also use ConstraintLayout for this, but row with weights works fine too.
Row(
Modifier.fillMaxWidth().padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Icon(
modifier = Modifier.weight(1f),
imageVector = Icons.Default.ArrowBack, contentDescription = "Back"
)
Text(
modifier = Modifier.weight(8f),
text = "Title",
textAlign = TextAlign.Center
)
Icon(
modifier = Modifier.weight(1f),
imageVector = Icons.Default.Delete,
contentDescription = "Delete"
)
}

Explaining coordinate's calculation with map()

Could someone explain the meaning of this value/position: 300-400/2+10. I know it makes that the red circle won't go out of the square but I don't really understand the calculation..? Is there a page where I can read how it works because I personally would do it like this
float redCircle = map(mouseX,0,width,116,485);
circle(redCircle,map(mouseY,0,height,114,485),20);
with one number positions and not a calculation like in the code. I tried to understand it but I don't. I would really appreciate it if someone could explain the proceed.
void setup() {
size(600, 600);
surface.setTitle("Mapping");
surface.setLocation(CENTER, CENTER);
}
void draw() {
background(0);
stroke(255);
fill(255, 255, 255);//weißer Kreis
circle(mouseX, mouseY, 20);
mouseMoved();
text("Maus X/Y:"+mouseX+"/"+mouseY, 250, 300); //Text für weiße Position
fill(255, 0, 0); //Roter Kreis
float posFromMouseX = map(mouseX, 0, width, 300-400/2+10, 300-400/2+400-10);
float posFromMouseY = map(mouseY, 0, height, 300-400/2+10, 300-400/2+400-10);
ellipse(posFromMouseX, posFromMouseY, 20, 20);
text("map to: "+posFromMouseX+" / "+posFromMouseY, 255, 320); //Text für rote Position
// Transparentes Rechteck in der Mitte
noFill();
rect(300-400/2, 300-400/2, 400, 400);
}
map() will adjust the scale of a number accordingly to a range.
For an example, if you have these values:
MouseX: 200
width: 1000
You can easily calculate that, if the screen had a width of 2000 your mouse X position would need to be 400 to be proportional.
But this is an easy example. In the code you pasted here, the same thing is happening, but the coordinates compared are:
The whole window
The white rectangle
The map() function takes 5 args:
map(value, start1, stop1, start2, stop2)
value: float: the incoming value to be converted
start1: float: lower bound of the value's current range
stop1: float: upper bound of the value's current range
start2: float: lower bound of the value's target range
stop2: float: upper bound of the value's target range
So... you can totally write this line without the calculations:
float posFromMouseX = map(mouseX, 0, width, 110, 300-400/2+400-10);
// is the same thing than:
float posFromMouseX = map(mouseX, 0, width, 110, 490);
The probables reasons to write it that way are:
The author may not have wanted to do the simple math
The author may want to know where these numbers come from later (seeing how they were calculated would help on this front)
The author may want to change the hardcoded numbers for variables and make his white rectangle's size dynamic later
Hope it makes sense to you. Have fun!

PIXI.Text as a hole but keeping stroke

I am trying to add text to a PIXI Application as a hole, cutting through the canvas showing what's underneath it, but need to keep the stroke around each letter. I am using a blendMode to cut the text through, but it removes the stroke as well. Is it even possible?
//hacked clearing blendmode
app.renderer.state.blendModes[20] = [0,
WebGLRenderingContext.ONE_MINUS_SRC_ALPHA];
//Adding text
var style = new PIXI.TextStyle({
fontFamily: 'din-condensed',
fontSize: '400px',
fontWeight: 400,
stroke: '#25556f',
strokeThickness: 5
});
var invertedText = new PIXI.Text(text, style);
invertedText.x = 0;
invertedText.y = (height / 2) - ($(heading).height() / 2);
invertedText.blendMode = 20;
app.stage.addChild(invertedText);

DrawText doesn't work, but Graphics::DrawString is ok

I am creating a bitmap in the memory which combine with an image and text. My code is:
HDC hdcWindow = GetDC();
HDC hdcMemDC = CreateCompatibleDC(hdcWindow);
HBITMAP hbmDrag = NULL;
if (!hdcMemDC) {
ReleaseDC(hdcWindow);
return NULL;
}
RECT clientRect = {0};
GetClientRect(&clientRect);
hbmDrag = CreateCompatibleBitmap(hdcWindow, 256, 256);
if(hbmDrag) {
SelectObject(hdcMemDC, hbmDrag);
FillRect(hdcMemDC, &clientRect, mSelectedBkgndBrush);
Graphics graphics(hdcMemDC);
// Draw the icon
graphics.DrawImage(mImage, 100, 100, 50, 50);
#if 1
CRect desktopLabelRect(0, y, clientRect.right, y);
HFONT desktopFont = mNameLabel.GetFont();
HGDIOBJ oldFont = SelectObject(hdcMemDC, desktopFont);
SetTextColor(hdcMemDC, RGB(255,0,0));
DrawText(hdcMemDC, mName, -1, desktopLabelRect, DT_CENTER | DT_END_ELLIPSIS | DT_CALCRECT);
#else
// Set font
Font font(hdcMemDC, mNameLabel.GetFont());
// Set RECT
int y = DEFAULT_ICON_HEIGHT + mMargin;
RectF layoutRect(0, y, clientRect.right, y);
// Set display format
StringFormat format;
format.SetAlignment(StringAlignmentCenter);
// Set brush
SolidBrush blackBrush(Color(255, 0, 0, 0));
// Draw the label
int labelWide = DEFAULT_ICON_WIDTH + mMargin;
CString labelName = GetLayOutLabelName(hdcMemDC, labelWide, mName);
graphics.DrawString(labelName, -1, &font, layoutRect, &format, &blackBrush);
#endif
}
DeleteDC(hdcMemDC);
ReleaseDC(hdcWindow);
return hbmDrag;
The image can be outputted to the bitmap success.
For the text, if I use "DrawText", it can't be shown in the bitmap although the return value is correct;
But Graphics::DrawString can output the text success.
I don't know the reason. Anybody can pls tell me?
Thanks a lot.
You are passing the DT_CALCRECT flag to DrawText(). This flag is documented as (emphasis mine):
Determines the width and height of the rectangle. If there are
multiple lines of text, DrawText uses the width of the rectangle
pointed to by the lpRect parameter and extends the base of the
rectangle to bound the last line of text. If the largest word is wider
than the rectangle, the width is expanded. If the text is less than
the width of the rectangle, the width is reduced. If there is only one
line of text, DrawText modifies the right side of the rectangle so
that it bounds the last character in the line. In either case,
DrawText returns the height of the formatted text but does not draw
the text.

What are a line's exact dimensions in JavaFX 2?

I'm wondering about a line's exact width in JavaFX 2.1.
Creating a straight line starting at point (10;10) and ending at point (20;10) should have an expected width of 10px. When reading the line's width a value of 11px is returned.
When starting the attached example and making a screenshot you'll see - at a higher zoom level - the line has a width of 12px and a height of 2px.
Using a LineBuilder does not make any difference. I event tried to apply a different StrokeType without success.
Creating a square with a side length of 10 returns the expected width of 10px.
Why does line.getBoundsInLocal().getWidth() return different values from those I see in the rendered result (screenshot)?
Why is there a difference concerning width when creating lines and polygons?
Example:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
public class ShapeWidthDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
Group root = new Group();
int startX = 10;
int startY = 10;
int length = 10;
Line line = new Line(startX, startY, startX + length, startY);
System.out.println("line width: " + line.getBoundsInLocal().getWidth());
//->line width: 11.0
System.out.println("line height: " + line.getBoundsInLocal().getHeight());
//->line height: 1.0
root.getChildren().add(line);
int startY2 = startY + 2;
Polygon polygon = new Polygon(
startX, startY2,
startX + 10, startY2,
startX + 10, startY2 + 10,
startX, startY2 + 10);
System.out.println("polygon width: " + polygon.getBoundsInLocal().getWidth());
//->polygon width: 10.0
System.out.println("polygon height: " + polygon.getBoundsInLocal().getHeight());
//->polygon height: 10.0
root.getChildren().add(polygon);
stage.setScene(new Scene(root, 100, 100));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
1) The line's end points are controlled by StrokeLineCap.
So with default stroke width you can get the "desired" values by
line.setStrokeType(StrokeType.CENTERED);
line.setStrokeLineCap(StrokeLineCap.BUTT);
... that the line has a width of 12px and a height of 2px.
You can get these values if StrokeType.OUTSIDE AND NOT StrokeLineCap.BUTT are applied. However the height remains in 2px even changing the stroke attributes. That's strange..
2) Its because of the polygon's stroke value is NULL by default. By setting it like polygon.setStroke(Color.RED); the stroke will render.
Think of the line itself as having no surface area whatsoever, it is infinitely thin. The only dimensions for the line come from the stroke.
When the StrokeType is CENTERED and the StrokeLineCap is SQUARE (the default values), then the area taken by the the line comes from evenly applying half the stroke width around the line in all directions.
In your example, you are drawing a horizontal line with a stroke of 1 at an integer co-ordinate. The JavaFX co-ordinate system is such that integer corners map to the lines between pixels. When you display a horizontal line with integer co-ordinates, the line itself crosses half of the two vertical pixels on either side of the line and ends up as a gray shaded antialiased line rather than a black line shading only one vertical pixel. Additionally, the StrokeLineCap of SQUARE applied at the line ends means that the stroke extends beyond the end points of the line by half the stroke width. This ends up shading a quarter of each pixel in the two pairs of pixels on either side of the line ends.
By offsetting the co-ordinates of the line by half a pixel, the line can be drawn such that it shades a single row of vertical pixels as now the line is falling along the middle of each pixel and the line stroke is extending to the top and bottom edges of each pixel. Additionally, setting the line cap to butt rather than square means that the shaded area for the line will not extend beyond the ends of the line.
Here is some sample code to demonstrate this. In the sample, only the last line completely shades exactly 10 pixels and no more. It is also the only line which has a width of 10 and integer co-ordinates in it's bounding box.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;
public class LineBounds extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) throws Exception {
double startX = 10;
double startY = 10;
double length = 10;
Line lineSpanningPixelsSquareEnd = new Line(startX, startY, startX + length, startY);
System.out.println("lineSpanningPixels (square) bounding box: " + lineSpanningPixelsSquareEnd.getBoundsInLocal());
startX = 10;
startY = 20;
length = 10;
Line lineSpanningPixelsButtEnd = new Line(startX, startY, startX + length, startY);
lineSpanningPixelsButtEnd.setStrokeLineCap(StrokeLineCap.BUTT);
System.out.println("lineSpanningPixels (butt) bounding box: " + lineSpanningPixelsButtEnd.getBoundsInLocal());
startX = 10;
startY = 29.5;
length = 10;
Line lineOnPixelsSquareEnd = new Line(startX, startY, startX + length, startY);
System.out.println("lineOnPixels (square) bounding box: " + lineOnPixelsSquareEnd.getBoundsInLocal());
startX = 10;
startY = 39.5;
length = 10;
Line lineOnPixelsButtEnd = new Line(startX, startY, startX + length, startY);
lineOnPixelsButtEnd.setStrokeLineCap(StrokeLineCap.BUTT);
System.out.println("lineOnPixels (butt) bounding box: " + lineOnPixelsButtEnd.getBoundsInLocal());
stage.setScene(
new Scene(
new Group(
lineSpanningPixelsSquareEnd, lineSpanningPixelsButtEnd, lineOnPixelsSquareEnd, lineOnPixelsButtEnd
), 100, 100
)
);
stage.show();
}
}
Output of the sample program is:
lineSpanningPixels (square) bounding box: BoundingBox [minX:9.5, minY:9.5, minZ:0.0, width:11.0, height:1.0, depth:0.0, maxX:20.5, maxY:10.5, maxZ:0.0]
lineSpanningPixels (butt) bounding box: BoundingBox [minX:10.0, minY:19.5, minZ:0.0, width:10.0, height:1.0, depth:0.0, maxX:20.0, maxY:20.5, maxZ:0.0]
lineOnPixels (square) bounding box: BoundingBox [minX:9.5, minY:29.0, minZ:0.0, width:11.0, height:1.0, depth:0.0, maxX:20.5, maxY:30.0, maxZ:0.0]
lineOnPixels (butt) bounding box: BoundingBox [minX:10.0, minY:39.0, minZ:0.0, width:10.0, height:1.0, depth:0.0, maxX:20.0, maxY:40.0, maxZ:0.0]

Resources