Preliminary Research on Font Size Adaptive Scheme under GDI+

At a certain moment, I suddenly discovered that the Three-Body Problem or AI are very similar things in essence. Even when facing any unknown field, human beings will always be divided into Adventists, Salvationists and Survivorists unconsciously. Regardless of the real motives of Musk and others to stop GPT-5, when the big language model (LLM) is coming with a wave of AIGC, will you, like many people, worry that one day it will be replaced by a machine? What about unemployment? Before, I mentioned with self-deprecation that I am a YAML engineer, a Markdown engineer, a Dockerfile engineer... and I will even become a Prompt engineer in the future. The way of programming is gradually shifting from DSL to natural language. I personally think that any low-end and repetitive work will eventually be replaced by machines, and irrational fields such as emotion, art, psychology, creativity... may become the last line of defense for human beings. Two years ago, Ke Jie lost to AlphaGo with a score of 0:3, and once lost control of his emotions in front of the chessboard. I think at that moment, he probably would not have thought that ChatGPT would appear two years later. In the "Spider-Man: No Home for Heroes" movie, Peter Parker said to Doctor Strange, "Do you know what is more magical than magic? It's mathematics ." I personally like this sentence very much, because in the face of absolute rationality, all skills are in vain, and more importantly, such a profound philosophy comes from a real case in life.

Electronic signature and mathematics

Well, while we say that low-end, repetitive jobs will eventually be replaced by machines, the real harsh reality is that we don't have as many jobs that require creativity, just as we don't need as many architects. After all, you can't imagine that there is no difference in the work that a person did five years ago and five years later, especially printing, which is very common in enterprise-level applications. In the past few years, the slogan of digital transformation of enterprises has been shouted, but in the end we have not waited for the real paperless, and enterprises are still fond of printing documents, as if there is no way to carry out business without this piece of paper. In this process, the company will hope that you can affix the company's seal on the documents, which creates the demand for the so-called "electronic signature". Of course, we do not consider the actual process of electronic signature application, encryption/decryption, anti-counterfeiting, etc., we just consider drawing it through GDI + . Considering that the seal has two shapes, round and oval, let's discuss the classification below.

round stamp

It can be noticed that a round seal usually consists of four parts, namely the top text, the five-pointed star in the center, the middle and lower part of the text, and the bottom text.

insert image description here

Among them, the text on the top indicates the company/organization/institution to which the seal belongs, and the text on the bottom indicates the 14-digit stamp number, both of which are distributed in an arc shape. How to achieve it? Let's take a look together. First, the outline of the circular stamp is a standard circle, which is very easy to draw:

// 从位图创建一个画布
var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
var g = Graphics.FromImage(bitmap);

// 绘制圆形边框 
var rect = new RectangleF(x, y, radius, radius);
var Pen pen = new Pen(Color.Red, 3.0f);
g.DrawEllipse(pen, rect);

For the five-pointed star in the center, we can use a path to fill it. At this point, the key to the problem is to find the five vertices of the five-pointed star on the circle. Obviously, the vertices of the pentagram satisfy the following geometric relations:

insert image description here

Using the knowledge of trigonometric functions, we can write the corresponding code very easily. Please note that the positive direction of the Y-axis of the coordinate system used in the computer is downward:

var Radius = rect.Width / 2 * 0.45;
var Center = new PointF(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
PointF[] points = new PointF[]
{
    // P0
    new PointF(Center.X, (float)(Center.Y - Radius)),
    // P1
    new PointF(
        (float)(Center.X + Radius * Math.Sin(72 * Math.PI / 180)), 
        (float)(Center.Y - Radius * Math.Cos(72 * Math.PI / 180))
    ),
    // P2
    new PointF(
        (float)(Center.X + Radius * Math.Sin(36 * Math.PI / 180)), 
        (float)(Center.Y + Radius * Math.Cos(36* Math.PI / 180))
    ),
    // P3
    new PointF(
        (float)(Center.X - Radius * Math.Sin(36 * Math.PI / 180)),
        (float)( Center.Y + Radius * Math.Cos(36 * Math.PI / 180))
    ),
    // P4
    new PointF(
        (float)(Center.X - Radius * Math.Sin(72 * Math.PI / 180)), 
        (float)(Center.Y - Radius * Math.Cos(72 * Math.PI / 180))
    ),
};

// 根据五个点生成一个封闭路径
var path = new GraphicsPath(FillMode.Winding);
path.AddLine(points[0], points[2]);
path.AddLine(points[2], points[4]);
path.AddLine(points[4], points[1]);
path.AddLine(points[1], points[3]);
path.AddLine(points[3], points[0]);
path.CloseFigure();

// 填充路径
g.RotateTransform(0);
g.FillPath(new SolidBrush(Color.Red), path);

Next, let's consider how to draw these two sections of text distributed in a circular arc. The mathematical knowledge needed here is the parametric equation of the circle and trigonometric functions. The basic idea is: select a starting angle, then calculate a step size based on the total angle and the number of characters, and then determine the angle corresponding to each character. For example, here assume the total angle of the upper half of the arc is 300 degrees and the total angle of the lower half of the arc is 60 degrees:

var center = new PointF(rect.X + rect.Width / 2.0f, rect.Y + rect.Height / 2.0f);
var fontToFit = new Font("宋体", 13, FontStyle.Bold, GraphicsUnit.Pixel);
var totalAngle = Math.PI * 5 / 3;
var stepAngle = totalAngle / (text1.Length + 1);
var startAngle = Math.PI * 4 / 3;
for (int i = 0; i < text.Length; i++)
{
    float angle = (float)(startAngle - (i + 1) * stepAngle);
    if (angle < 0) angle += (float)Math.PI * 2;
    var point = new PointF(
        center.X + radius * (float)Math.Cos(angle), 
        center.Y - radius * (float)Math.Sin(angle)
    );

    g.DrawString(text1[i].ToString(), fontToFit1, brush, point.x, point.y);
}

We know that in the definition of trigonometric functions, the counterclockwise direction is the positive direction. Therefore, for the arc text in the upper part, it is only necessary to subtract the corresponding number of steps from the starting angle in turn. In order to make it easier to calculate the angle later, the negative angle will be uniformly converted into a positive angle. The next thing is logical, because you can use trigonometric functions to calculate the corresponding coordinates. At this time, we only need to call the DrawString function at the specified position to draw each character:

insert image description here

However, you will soon find a problem, that is, the direction of these words is not always "facing" your realization like a general seal. At this point, you will use the third mathematical knowledge, that is: when a point becomes the origin, how will the angle between it and the positive direction of the X-axis change? Why do you need this knowledge? Because we need to do a translation transformation and a rotation transformation for each character, so as to achieve our goal, that is: no matter which direction you look at these characters, it is "positive" for you, and its code The implementation is as follows:

var center = new PointF(rect.X + rect.Width / 2.0f, rect.Y + rect.Height / 2.0f);
var fontToFit = new Font("宋体", 13, FontStyle.Bold, GraphicsUnit.Pixel);
var totalAngle = Math.PI * 5 / 3;
var stepAngle = totalAngle / (text1.Length + 1);
var startAngle = Math.PI * 4 / 3;
for (int i = 0; i < text.Length; i++)
{
    float angle = (float)(startAngle - (i + 1) * stepAngle);
    if (angle < 0) angle += (float)Math.PI * 2;
    var point = new PointF(
        center.X + radius * (float)Math.Cos(angle), 
        center.Y - radius * (float)Math.Sin(angle)
    );

    g.TranslateTransform(point.X, point.Y);
    var transformAngle = (float)(angle * 180 / Math.PI + 90);
    if (transformAngle > 360) transformAngle -= 360;

    // 注意:RotateTransform() 方法旋转方向是顺时针方向,所以,要用 360 度减去当前角度
    // 印章上方的文字需要正对着外侧,所以,要再加上 180 度
    transformAngle = 360 - transformAngle + 180;
    g.RotateTransform(transformAngle);
    g.DrawString(text[i].ToString(), fontToFit, brush, 0, 0, format);
    g.ResetTransform();
}

The key here is TranslateTransform()and RotateTransform()Two methods, this is the translation transformation and rotation transformation we mentioned above. Why should it be handled this way? Because what we want to see is that the text rotates to the "correct" direction and keeps the position unchanged. If there is no translation conversion, it will become point A rotating around point B, which does not meet our expectations. In short, when everything is developing in the expected direction, we can use this technique to "draw a scoop according to the gourd". It should be noted that the "positive direction" of the arc text at the bottom is downward, so when doing When rotating and transforming, the two will differ by 180 degrees. In this regard, I want to say that mathematics is really easy to use:

insert image description here

In contrast, drawing the middle and lower part of the text is very simple, because it is drawn on a rectangular range, and the only thing you need is a Pythagorean theorem.
In theory, as long as you integrate the above code fragments, you can draw a relatively perfect seal. My personal experience is that the real difficulty is often the actual size of the seal, printing size, DPI, resolution and other objective factors. .

oval seal

Well, let's talk about the drawing of the ellipse stamp. The principle is basically the same, and the corresponding mathematical knowledge is the parametric equation of the ellipse. I don’t know if you still remember the concepts of conic section, eccentricity, focus, etc. When the blogger wrote this blog, he did go back and review these knowledge again. As a programmer, I am often "educated" by others about the importance of business, but for me, business is probably synonymous with chaotic and turbulent times. Compared with these artificially imagined and constructed things, I prefer wind and sand. Things that are close to nature and the universe, such as stars and stars, for me, mathematics is like this. Well, here is the code snippet for drawing arc text, for your reference:

var a = rect.Width / 2 * 0.8f;
var b = rect.Height / 2 * 0.8f;
var center = new PointF(rect.X + rect.Width / 2.0f, rect.Y + rect.Height / 2.0f);
var fontToFit = new Font("宋体", 13, FontStyle.Bold, GraphicsUnit.Pixel);
var totalAngle = Math.PI * 5 / 3
var stepAngle = totalAngle / (text1.Length + 1);
var startAngle = Math.PI * 4 / 3
for (int i = 0; i < text.Length; i++)
{
    float angle = (float)(startAngle - (i + 1) * stepAngle);
    if (angle < 0) angle += (float)Math.PI * 2;

    // 利用椭圆参数方程计算坐标
    var point = new PointF(
        center.X + a * (float)Math.Cos(angle), 
        center.Y - b * (float)Math.Sin(angle)
    );

    g.TranslateTransform(point.X, point.Y);
    var transformAngle = (float)(angle * 180 / Math.PI + 90);
    if (transformAngle > 360) transformAngle -= 360;

    // 注意:RotateTransform() 方法旋转方向是顺时针方向,所以,要用 360 度减去当前角度
    // 印章上方的文字需要正对着外侧,所以,要再加上 180 度
    transformAngle = 360 - transformAngle + 180;
    g.RotateTransform(transformAngle);
    g.DrawString(text[i].ToString(), fontToFit1, brush, 0, 0, format);
    g.ResetTransform();
}

It can be seen that essentially only the radius needs to be replaced by the semi-major axis a and the semi-minor axis b. Considering that the circle is a special form of the ellipse, isn't this way of knowing from the special to the general interesting? Similarly, here is an rendering of an oval stamp:

insert image description here

So far, the author has taught me everything about how to draw a seal through GDI+. In this process, the part I enjoy the most is precisely those ordinary mathematical knowledge. When you think that AI will replace human beings one day At that time, I thought that this represented the victory of absolute rationality, just like AlphaGo that made Ke Jie cry, without any skills, it was just the inevitable result after countless calculations. In a sense, mathematics On the contrary, it is the simplest science in the universe, isn't it? If there are any other things that are closer to "metaphysics" in artificial intelligence, I think it is probably because we have not been able to explain clearly from the beginning to the end, how does the neural network imitating neurons generate "consciousness" step by step? And all of this is like, you do know that ChatGPT has given you a satisfactory answer, but what you still don’t know is, what kind of thinking process did it go through before it can give you an answer that fits your psychological expectations? Answer? In a trance, I feel that it meets the "high emotional intelligence" standard in the eyes of humans. Even if it knows nothing about you, it can still speak words that make you "comfortable". If the language itself is full of such confusion Sex, so what is the emotion that humans value most?

font adaptation scheme

Well, now let's think about it a bit longer. When we implement a relatively general stamp drawing algorithm, we will hope to generate different stamps by configuring these texts. At this time, a new problem arises: How can characters of different lengths be compatible when the size of the stamp is fixed (with relevant standards)? An easy solution is to modify the font size. For example, shrink the font when there are many characters, and enlarge the font when there are few characters. So, what I want to share next is to dynamically adjust the font size to achieve font adaptation.

Based on width and height

The first solution is based on width and height, that is, the font is drawn in a rectangular area, and the middle and lower parts of the seal are usually used to indicate the purpose of the seal. At this time, we can use the MeasureString method to measure the width of the entire string, and set It is compared with the width of the current rectangle. If the actual width is greater than the width of the current rectangle, the font size needs to be reduced; if the actual width is smaller than the width of the current rectangle, the font size needs to be increased. Of course, you can give a minimum font size when zooming out, because you want to ensure that others can see the text on the stamp clearly; when zooming in, you need to consider the height of the rectangle, because you want to ensure that the elements on the stamp will not overlap each other. Here is a basic implementation:

float ScaleFontSizeByContainerSize(Graphics g, string text, Font font, SizeF size) {
    var fontSize = font.Size;

    // 对字体缩小时需要考虑最小的字体大小
    var measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    while (measuredSize.Width > size.Width) {
        fontSize -= 0.5f;
        if (fontSize <= MIN_FONT_SIZE) break;
        measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    }

    // 对字体放大时需要考虑高度的问题
    measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    while (measuredSize.Width < size.Width && measuredSize.Height < size.Height) {
        fontSize += 0.5f;
        measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    }

    return fontSize;
}

As shown in the figure, the following figure shows that under the same size, the text is dynamically scaled according to the number of characters in different font sizes:

insert image description here

Perimeter based

The second solution is based on the perimeter, mainly for the characters distributed in a circular arc in the stamp. At this time, the width and height are not enough to evaluate whether the characters can be "properly" distributed on the stamp, so we can try to use perimeter for comparison. According to the idea of ​​calculus, we can roughly think that the sum of the width of each character is approximately equal to the length of the corresponding solitary line. In this case, we can consider the arc length formula and the perimeter formula of an ellipse. When the text width is smaller than the circumference, it indicates that the font can be enlarged; when the text width is greater than the circumference, it indicates that the font can be reduced a little. Similarly, here is a basic implementation:

float ScaleFontSizeByPerimeter(Graphics g, string text, Font font, float radius, float angle) {
    var fontSize = font.Size;
    
    // 圆形周长公式
    var perimeter = angle * Math.PI * radius / 180;

    // // 椭圆周长公式
    // var h = Math.Pow((a - b) / (a + b), 2);
    // var c = Math.PI * (a + b) * (1 + (3 * h / (10 + (4 - 3 * h))));
    // var perimeter = c * angle / 360;

    // 对字体缩小时需要考虑最小的字体大小
    var measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    while (measuredSize.Width > perimeter) {
        fontSize -= 0.1f;
        if (fontSize <= MIN_FONT_SIZE) break;
        measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    }

    // 对字体放大时需要考虑高度的问题
    measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    while (measuredSize.Width < perimeter) {
        fontSize += 0.1f;
        if (fontSize >= MAX_FONT_SIZE) break;
        measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    }

    return fontSize;
}

As shown in the figure, the following figure shows that under the same size, the text is dynamically scaled according to the number of characters in different font sizes:

insert image description here

Summary of this article

When writing this blog, it is actually an eventful time. If it refers to Makoto Shinkai’s new movie "Journey to Suzuya", I will probably write about regret and self-reconciliation; if it refers to ChatGPT, I will probably write about my views on artificial intelligence; if it refers to the breakup of Jing Tian and Zhang Jike I will probably write about my understanding of human emotions; if it refers to the resignation incident of CETC, I will probably write about my thoughts on productivity/production relations... But when many things are entangled , any kind of emotion is destined to be unable to survive alone, so I decided to write something that can be controlled. As Peter Parker said, " Mathematics is more magical than magic ". In an era where uncertainty is far greater than certainty, it is a kind of luck to be able to feel the beauty of the mathematical world again, because all formulas/theorems will lead you to a certain place, just like when I finish drawing the circular seal, I can quickly draw the oval seal, it will not add the slightest mental burden, and the business process that human beings are keen to build , there will always be defects of one kind or another. Perhaps, until this moment, I was able to understand the paranoia of scientists eager to describe the world with a formula. Because, looking for certainty in an uncertain world is fascinating enough in itself, just like the prompt words you input into the AI ​​model, they may themselves be random and uncertain, but what you need may is a definite result. After all, the world has long been so complicated that even the choice itself is a difficult thing, right?
It is really a kind of luck to feel the beauty of the mathematical world again, because all the formulas/theorems will lead you to a certain place, just like when I finished drawing the circular stamp, I can quickly draw the oval stamp, It will not add the slightest mental burden, and the business processes that humans are keen to build will always have defects of one kind or another. Perhaps, until this moment, I was able to understand the paranoia of scientists eager to describe the world with a formula. Because, looking for certainty in an uncertain world is fascinating enough in itself, just like the prompt words you input into the AI ​​model, they may themselves be random and uncertain, but what you need may is a definite result. After all, the world has long been so complicated that even the choice itself is a difficult thing, right?

Guess you like

Origin blog.csdn.net/qinyuanpei/article/details/130121450