PangoCairo text cut-off when using PANGO_ALIGN_MIDDLE - c++

On a C++ game, we're using Pango to render text with cairo and from there to an OpenGL texture.
I noticed this problem recently while working on implementing text-wrapping via Pango.
What I'm doing is calculating the width by translating our own coordinate system to pixels and then using that as a fraction of the window width in PANGO UNITS, or in code:
float screenEdge = _w * m * static_cast<float>(PANGO_SCALE);
float tempMaxWidth = _owner->m_wrapping.m_maxWidth;
tempMaxWidth = std::min(_owner->m_wrapping.m_maxWidth, (0.48f - _owner->dimensions().x1()));
float wrapWidth = screenEdge * tempMaxWidth;
maxWidth = static_cast<int>(std::round(wrapWidth));
pango_layout_set_width(_owner->m_pangoLayout.get(), maxWidth);
Then we get a rectangle from the layout an pass that to cairo with:
float border = 2.0f;
PangoRectangle lRec;
pango_layout_get_pixel_extents(_owner->m_pangoLayout.get(), nullptr, &lRec);
m_width = (lRec.width + border); // Add twice half a border for margins
m_height = (lRec.height + border);
std::unique_ptr<cairo_surface_t, decltype(&cairo_surface_destroy)> m_cairoSurface(
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, m_width, m_height),
&cairo_surface_destroy);
std::unique_ptr<cairo_t, decltype(&cairo_destroy)> m_cairoDc(
cairo_create(m_cairoSurface.get()),
&cairo_destroy);
cairo_set_antialias(m_cairoDc.get(), CAIRO_ANTIALIAS_FAST);
cairo_push_group_with_content (m_cairoDc.get(), CAIRO_CONTENT_COLOR_ALPHA);
cairo_set_operator(m_cairoDc.get(), CAIRO_OPERATOR_SOURCE);
// Add Pango line and path to proper position on the DC
cairo_move_to(m_cairoDc.get(), (0.5f * border), (0.5f * border)); // Margins needed for border stroke to fit in
But the output, as you can see below, is cut-off; this does not happen if I use PANGO_ALIGN_LEFT
Searching around here, I found Cairo + Pango layout non-left alignment
Which appears related to my issue but does not quite provide a solution (or I didn't get it properly); what I tried was to calculate the different between the x and y coordinates of the logical and ink rectangles and adding that to cairo_move_to but that made no real difference in the output, so I just pasted the original code here.

Related

Why does gdi + draw curves incorrectly?

I want to write a parser for svg files and render vector graphics with gdi+, but when I try to inject data from the svg file into the code,
turns out to be incorrectly rendered
bezier curve. The edges of the curve are uneven.
I tried changing the miterlimit values of the SetMiterLimit function of the Pen class, and tried other functions, but nothing changes. I display the image through opengl. For comparison, I'm attaching screenshots: how the curve looks in browsers and in gdi + with and without anti-aliasing. What could be the problem?
svg code:
<path fill="none" stroke="#000000" stroke-width="32" stroke-miterlimit="10" d="M168,37c0,35.4,39.4,64,88,64"/>
c++ code:
Rect paintRect(0, TbHeight, mw->getClRect().right, mw->getClRect().bottom);
Bitmap bmp(paintRect.Width, paintRect.Height - TbHeight);
Graphics* graphics = new Graphics(&bmp);
graphics->SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
graphics->SetSmoothingMode(SmoothingModeAntiAlias);
float _x = 168;
float _y = 37;
PointF p[] =
{
PointF(_x, _y),
PointF(_x + 0, _y + 35.4),
PointF(_x + 39.4, _y + 64),
PointF(_x + 88, _y + 64)
};
pen.SetWidth(32.f);
graphics->DrawBeziers(&pen, p, 4);
screenshots:
svg at browsers
without anti-aliasing gdi+ with anti-aliasing gdi+

Getting absolute rectangle coordinates after direct2d translation and scale

I'm using direct2d to draw a bitmap (play a video) in a window, and I want to get the absolute coordinates for any position in the playing space, whether transformations are applied or not. So if the resolution is 1280x720, then by hovering the cursor over the image, I should get values like x = 0 ... 1280, y = 0 ... 720.
The positions of the total video area are in the variable m_rcLiveWindowPos, while the variable m_rcDstVideoRect contains the positions of the actual video after adjusting for the aspect ratio. Finally, m_rcSrcVideoRect is just the video resolution (ex: left=0, top=0, right=1280, bottom=720).
Below, I applied a translation and then a scale to the renderTarget. The rawScaleFactor is a number representing the amount to scale the video: if rawScaleFactor=1, then the video should be played at 100%. If 2, then at 200%.
This all works great -- the video zooms in properly and I can click and drag the video around. The problem is that I want to get the absolute x and y coordinates of the video resolution while my cursor is hovering over the video. The first definitions of mousePosInImage work for videos with no zoom/panning with the m_rcDstVideoRect sitting in a "fitted" position, but the values are incorrect for a zoomed-in video.
if (rawScaleFactor != 0)
{
// Make the dragging more precise based on the scaling factor.
float dragPosX = (float)m_rawScaleOffsetX / (rawScaleFactor * 2.0f);
float dragPosY = (float)m_rawScaleOffsetY / (rawScaleFactor * 2.0f);
D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(dragPosX, dragPosY);
// Get the center point of the current image.
float centerPointX = float(m_rcLiveWindowPos.Width()) / 2;
float centerPointY = float(m_rcLiveWindowPos.Height()) / 2;
// Calculate the amount that the image must scaled by.
D2D1ScaleFactor = ((float)m_videoResolution.width / (float)(m_rcDstVideoRect.right - m_rcDstVideoRect.left)) * (float)rawScaleFactor;
D2D1_MATRIX_3X2_F scale = D2D1::Matrix3x2F::Scale(D2D1::Size(D2D1ScaleFactor, D2D1ScaleFactor),
D2D1::Point2F(centerPointX, centerPointY));
// First translate the image, then scale it.
m_pJRenderTarget->SetTransform(translation * scale);
int32_t width = ((int32_t)m_videoResolution.width);
int32_t height = ((int32_t)m_videoResolution.height);
// This works for non-zoomed in video:
m_mousePosInImageX = int32_t(width * (rawMousePosX - m_rcDstVideoRect.left) / (m_rcDstVideoRect.right - m_rcDstVideoRect.left));
m_mousePosInImageY = int32_t(height * (rawMousePosY - m_rcDstVideoRect.top) / (m_rcDstVideoRect.bottom - m_rcDstVideoRect.top));
// Does not work for all cases...
m_mousePosInImageX = int32_t((centerPointX * D2D1ScaleFactor) - (centerPointX) + (m_mousePosInImageX / D2D1ScaleFactor));
m_mousePosInImageY = int32_t((centerPointY * D2D1ScaleFactor) - (centerPointY) + (m_mousePosInImageY / D2D1ScaleFactor));
}
m_pJRenderTarget-> DrawBitmap(m_pJVideoBitmap,
m_rcDstVideoRect,
1.0f,
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
m_rcSrcVideoRect);
I need a way to "reflect" the changes that SetTransform() did in the mousePosInImage variables.

Render FreeType text with flipped ortho, difference between top and baseline of glyph

I am working on a project where I implement a FreeType rendering object to draw text of which the rendering environment is specified with an orthographic projection matrix:
glm::ortho(0, Width, Height, 0);
This makes sure the coordinates are similar to standard GUI systems with (0,0) being the top-left part of the window instead of the bottom-left.
However when rendering using FreeType, things get difficult, because FreeType operates with their origin at the bottom-left of a glyph (minus the descender). My issue is similar to https://stackoverflow.com/questions/25353472/render-freetype-gl-text-with-flipped-projection but no answer was yet provided and his solution was not to my liking (the used library is also slightly different, I assume he is using a wrapper).
So I render my text as follows:
renderText("Testing 123 if text performs sufficiently", 0.0f, 0.0f, 1.0f, 1.0f);
Of which renderText function contains:
renderText(const GLchar *text, GLfloat x, GLfloat y, GLfloat sx, GLfloat sy)
{
[...]
GLfloat xpos = x + glyph->bitmap_left * sx;
GLfloat ypos = y - glyph->bitmap_top * sy;
GLfloat w = glyph->bitmap.width * sx;
GLfloat h = glyph->bitmap.rows * sy;
// Update VBO
GLfloat vertices[4][4] = {
{ xpos, ypos, 0, 0 },
{ xpos + w, ypos, 1, 0 },
{ xpos, ypos + h, 0, 1 },
{ xpos + w, ypos + h, 1, 1 }
};
[...]
}
If I render it like this, it will render the text below the y coordinate of 0 so it won't be visible unless I add an offset to the y coordinate. So looking at FreeType's glyph metrics:
I want to offset the y position by a positive amount equal to the difference between the origin and the top of the glyph image so it always neatly renders the text at my given position. Looking at the image I believe this to be the yMax value so I added the following statement to the code before updating the VBO:
ypos += (glyph->face->bbox.yMax >> 6) * sy;
Which seemed to fix the issue when I loaded the FreeType glyphs with font size 24, but as soon as I tried to use different font sizes it failed to work as this image shows:
As you can see, it clearly doesn't work as I thought it would. I've been thouroughly searching through FreeType's documentation if I was missing something, but I could not find it. Am I using the wrong metrics (using Ascender didn't work as well)?
I want to offset the y position by a positive amount equal to the difference between the origin and the top of the glyph image so it always neatly renders the text at my given position. Looking at the image I believe this to be the yMax value so I added the following statement to the code before updating the VBO:
ypos += (glyph->face->bbox.yMax >> 6) * sy;
In actuality, yMax is not what you are interested in. You could use yMax - yMin to find the height of your glyph, but that is really all it is good for.
From the FreeType 2 API documentation, FT_GlyphSlotRec::bitmap_top is:
The bitmap's top bearing expressed in integer pixels. Remember that this is the distance from the baseline to the top-most glyph scanline, upwards y coordinates being positive.
Look at the image you included in your question again, that is effectively bearingY. Your problem is that you are subtracting this value from your ypos when you should not be. You do need the value as I will explain below, but you definitely do not want to subtract it.
If you eliminate bitmap_top from your calculation of ypos you get the following:
Which is obviously incorrect because it ignores differences in ascent between each character in your string.
Now, take a close look at the following correctly aligned diagram:
In the diagram above, I have illustrated your string's top-most line in red, bottom-most in green and the baseline for all of your glyphs in grey.
As you can see, the capital letter 'T' has the greatest ascent and this generalization holds true for most fonts. Directly below the red line, I have illustrated the difference in ascent between capital 'T' and the current letter as the yellow area. This is the important quantity that you must calculate to properly align your string.
The yellow region in the correctly aligned figure above can be calculated thus:
Chars['T'].bitmap_top - glyph->bitmap_top
If you stop subtracting glyph->bitmap_top from ypos and add the value above, you should get the same results as the correctly aligned diagram.
For bonus points, if you want to align your text to the bottom of the screen, the concept is very similar only you are interested in the difference between the character with the greatest descent (often lowercase 'g') and the current character. That is the distance between the grey baseline and the green line and can be expressed as height - bearingY in your glyph metrics figure.
You should be able to compute descent using this:
(glyph->metrics.height >> 6) - glyph->bitmap_top // bitmap_top is in integer coords

Text in Correct FontSize C++ Api hDC

I'm working with an older C++ application and I need to draw text centered and rotated on an hDC. I've got the centered & rotated part working but the font that gets written to the Image is smaller than I'm expecting. If you take an Image and edit it or a word document and add a line of of Text "COPY" in font size 128 it fills up most of the width of the page. With my code it only covers about 1/3 of the page. What am I missing or doing wrong?
UINT nOptions = 0;//DT_CENTER;
RECT rect = {0,0, BITMAPWIDTH(&WatermarkHandle), BITMAPHEIGHT(&WatermarkHandle)}; //{0,0,FileInfo.Width, FileInfo.Height};
SetMapMode(hdcWatermark, MM_TEXT);
LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lstrcpy(lf.lfFaceName, &sWatermarkFontName[0]);
lf.lfHeight = -MulDiv(lWatermarkFontSize, GetDeviceCaps(hdcWatermark, LOGPIXELSY), 72);
lf.lfEscapement = lWatermarkAngle * 10;
lf.lfOrientation = lf.lfEscapement;
lf.lfClipPrecision = CLIP_LH_ANGLES;// || CLIP_TT_ALWAYS;
lf.lfWeight = FW_THIN;
SetTextColor(hdcWatermark, black);
DrawRotatedText(hdcWatermark, &sWatermarkText[0], &rect, lf, lWatermarkAngle, nOptions);
void DrawRotatedText(HDC hdc, char *str, LPRECT rect, LOGFONT lf, double angle, UINT nOptions)
{
// convert angle to radian
double pi = 3.141592654;
double radian = (angle / 180 * pi);
HFONT hFontText = CreateFontIndirect(&lf);
HFONT hOldFontText = (HFONT)SelectObject(hdc, hFontText);
SIZE TextSize;
GetTextExtentPoint32(hdc, str, strlen(str), &TextSize);
// get the center of a not-rotated text
POINT center;
center.x = TextSize.cx / 2;
center.y = TextSize.cy / 2;
POINT rcenter;
rcenter.x = long(cos(radian) * center.x - sin(radian) * center.y);
rcenter.y = long(sin(radian) * center.x + cos(radian) * center.y);
// finally draw the text and move it to the center of the rectangle
SetTextAlign(hdc, TA_BASELINE);
SetBkMode(hdc, TRANSPARENT);
long lx = rect->left + ((rect->right - rect->left) / 2) - rcenter.x;
long ly = rect->top + ((rect->bottom - rect->top) / 2) + rcenter.y;
SetGraphicsMode(hdc,GM_ADVANCED);
ExtTextOut(hdc, lx, ly, nOptions, rect, str, strlen(str), NULL);
DeleteObject(hFontText);
SelectObject(hdc, hOldFontText);
return;
}
It all depends on what you mean by a "page".
A point is a physical unit of measure, just like an inch or a millimeter. In most modern contexts it's equal to 1/72 of an inch.
A page in Word corresponds to an actual piece of paper with physical dimensions. After subtracting out the margins it might be 6 inches wide, or 432 points. This will be zoomed by an amount to fill the screen.
Sizing a screen is a little more nebulous, since monitor sizes and resolutions may differ. Long ago Windows standardized on 96 pixels per inch for a default, as this was close enough to the typical monitor connected to a Windows system. This means a 1920x1080 monitor is considered by Windows to be 20 inches wide, or 1440 points, no matter how physically large it is.
In Word's case the screen is 432 points wide, while your program considers it to be 1440 points wide. This is very close to the 1/3 ratio you report. If you set Word to an exact 100% zoom the rendered text should be identical because the page will shrink.
The answer is to do as Word does, and zoom in by making everything 3x larger.
Edit: In your case it seems you want to emulate what Word is doing and show a facsimile of a page. That makes it even easier.
The formula given by Microsoft was from a time when floating point arithmetic was slow and avoided whenever possible. That's not the case today. The equivalent is:
lfHeight = -(int)(points * dpi / 72);
The question is what to use for dpi? You can calculate it directly! Since you know the size of the page you're displaying and the width of the window in pixels, it's a simple division:
double dpi = pixelWidth / (double)inches;
If you need to know what the effective zoom is, that's also a simple division: zoom = dpi / GetDeviceCaps(hdc, LOGPIXELSY).

Converting Window coordinates to Axis coordinates in OpenGL

Im creating a simple program in OpenGL to draw rectangles with the mouse. My goal is to click somewhere, drag the mouse and create a rectangle just like you do in paint or any other design program.
I have a view defined like:
glMatrixMode(GL_PROJECTION);
glOrtho(AXIS_X_MIN, AXIS_X_MAX, AXIS_Y_MIN, AXIS_Y_MAX, AXIS_Z_MIN, AXIS_Z_MAX);
and a window defined this way:
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
How can I convert the window coordinates which go from 0 to WINDOW_WIDTH and from 0 to WINDOW_HEIGHT into axis coordinates and respective quadrants?
Try:
double x = x_mouse / (double) WINDOW_WIDTH
* (AXIS_X_MAX - AXIS_X_MIN) + AXIS_X_MIN;
double y = (1 - y_mouse / (double) WINDOW_HEIGHT)
* (AXIS_Y_MAX - AXIS_Y_MIN) + AXIS_Y_MIN;
If you don't want to make the calculations by "hand", you can always check this small article, that makes use of a function from GLU library that shall do this internally.
http://steinsoft.net/index.php?site=Programming/Code%20Snippets/OpenGL/no8
gluUnProject — transforms map window coordinates to object coordinates.
To be honest, I barely have any idea of how it works. You can check it out here: OpenGL - gluUnProject
Also if you try this it should work:
float coorX = mouseX * width / WINDOW_WIDTH + AXIS_X_MIN;
float coorY = mouseY * heigth/ WINDOW_HEIGHT + AXIS_Y_MIN;