Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

文字列の描画で水平タブが消える場合がある #1002

Closed
Raclamusi opened this issue Apr 23, 2023 · 6 comments
Closed

文字列の描画で水平タブが消える場合がある #1002

Raclamusi opened this issue Apr 23, 2023 · 6 comments

Comments

@Raclamusi
Copy link
Member

Font 変数の宣言時に指定したフォントサイズと異なるサイズで文字列を描画するとき、水平タブの分のスペースが空かない場合があります。

主な原因は、 Siv3D/src/Siv3D/Font/GlyphCache/GlyphCacheCommon.cppGetTabAdvance() での double から int32 へのキャストの際に、浮動小数点数の精度不足により発生する意図しない切り捨てであると考えられます。

原因箇所

double GetTabAdvance(const double spaceWidth, const double scale, const double baseX, const double currentX, const int32 indentSize)
{
const double maxTabWidth = (spaceWidth * scale * indentSize);
const double newX = baseX + static_cast<int32>(((currentX + maxTabWidth) - baseX) / maxTabWidth) * maxTabWidth;
return (newX - currentX);
}

問題の様子
Font 変数をフォントサイズ 20 で宣言し、上からフォントサイズ 20~29 を指定して描画した様子を下図に示す。
すべて U"|\t\t\t\t\t\t\t\t\t\t|" を出力しているが、フォントサイズを 21, 22, 23, 24, 26, 27, 28 と指定した時に水平タブが一部消滅しているように見える。

20230423-222348-290

再現コード

# include <Siv3D.hpp> // OpenSiv3D v0.6.9

namespace s3d
{
	double GetTabAdvance(const double spaceWidth, const double scale, const double baseX, const double currentX, const int32 indentSize);
}

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	const Font font{ FontMethod::MSDF, 20 };

	const double spaceWidth = font(U" ").region().w;
	const double verticalLineWidth = font(U'|').region().w;

	while (System::Update())
	{
		for (int32 i = 0; i < 10; ++i)
		{
			constexpr double baseX = 30;
			const double size = 20 + i;
			double x = baseX;
			const double y = 40 + 40 * i;

			font(U"|\t\t\t\t\t\t\t\t\t\t|").draw(size, Arg::leftCenter(x, y), Palette::Black);

			const double scale = (20.0 + i) / font.fontSize();
			x += verticalLineWidth * scale;
			for (int32 j = 0; j < 10; ++j)
			{
				const double w = GetTabAdvance(spaceWidth, scale, baseX, x, 8);
				Line{ x + 1, y, x + w - 2, y }.drawArrow(0.5, SizeF{ 5, 5 }, (w < 1 ? Palette::Red : Palette::Black));
				x += w;
			}
		}
	}
}
@Reputeless
Copy link
Member

ご報告ありがとうございます。調査します。

@Reputeless Reputeless added this to Investigating in v0.6 Roadmap Apr 23, 2023
@Raclamusi
Copy link
Member Author

具体的なケースの説明です。

20230505-171633-488

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	const Font font{ FontMethod::MSDF, 20 };

	const double tabWidth = font(U"\t").region(26).w;

	while (System::Update())
	{
		{
			font(U"\t")
				.drawBase(26, 50, 70, Palette::Darkblue)
				.drawFrame(1, Palette::Darkred);
			font(UR"("う\t笑" を表示)")
				.drawBase(180, 70, Palette::Black);

			font(U"")
				.drawBase(26, 120, 140, Palette::Darkblue)
				.stretched(0, tabWidth, 0, 0)
				.drawFrame(1, Palette::Darkred);
			font(UR"("う" の幅に '\t' の幅を足して、)")
				.drawBase(250, 140, Palette::Black);

			Line{ 120, 160, Arg::direction(tabWidth, 0) }
				.drawArrow(5, SizeF{ 10, 10 }, Palette::Red);

			font(U"\t")
				.drawBase(26, 120, 200, Palette::Darkblue)
				.drawFrame(1, Palette::Darkred);
			font(UR"('\t' の幅を単位に切り捨てる。)")
				.drawBase(250, 200, Palette::Black);
			font(U"\t")
				.drawBase(26, 120, 250, Palette::Darkblue)
				.drawFrame(1, Palette::Darkred);
		}
		{
			font(U"\t\t")
				.drawBase(26, 50, 330, Palette::Darkblue)
				.drawFrame(1, Palette::Darkred);
			font(UR"("う\t\t笑" を表示)")
				.drawBase(180, 330, Palette::Black);

			auto region = font(U"\t")
				.drawBase(26, 120, 400, Palette::Darkblue);
			region.w += tabWidth;
			region
				.drawFrame(1, Palette::Darkred);
			font(UR"("う\t" の幅に '\t' の幅を足して、)")
				.drawBase(280, 400, Palette::Black);

			Line{ 120, 420, Arg::direction(tabWidth, 0) }
				.drawArrow(5, SizeF{ 10, 10 }, Palette::Red)
				.movedBy(tabWidth, 0)
				.drawArrow(5, SizeF{ 10, 10 }, Palette::Red);

			font(U"\t\t")
				.drawBase(26, 120, 460, Palette::Darkblue)
				.drawFrame(1, Palette::Darkred);
			font(
				UR"('\t' の幅を単位に切り捨てる。)" U"\n"
				UR"(ここで、("う\t\t" の幅)÷('\t' の幅) が)" U"\n"
				UR"({} であるので、'\t' が1つ消滅!)"_fmt(region.w / tabWidth)
			)
				.drawBase(280, 460, Palette::Black);
			font(U"\t\t")
				.drawBase(26, 120, 510, Palette::Darkblue)
				.drawFrame(1, Palette::Darkred);
		}
	}
}

@Reputeless
Copy link
Member

追加情報ありがとうございます!助かります。

@Raclamusi
Copy link
Member Author

タブの進みの計算結果が 0 になるときにインデントの深さを1つ増やして計算しなおすことで解決できそうです。

double GetTabAdvance(const double spaceWidth, const double scale, const double baseX, const double currentX, const int32 indentSize)
{
	const double maxTabWidth = (spaceWidth * scale * indentSize);
	const int32 indentLevel = static_cast<int32>(((currentX + maxTabWidth) - baseX) / maxTabWidth);
	double newX = (baseX + indentLevel * maxTabWidth);
	// 戻り値が 0 になるようなときは精度不足
	if ((newX - currentX) == 0)
	{
		// インデントレベルを1つ増やして計算しなおす
		newX = (baseX + (indentLevel + 1) * maxTabWidth);
	}
	return (newX - currentX);
}

screenshot

@Reputeless
Copy link
Member

修正方法の調査ありがとうございます!
v0.6.11 で採用したいと思います。Pull-request も歓迎です。

@Raclamusi
Copy link
Member Author

了解です。
PR 投げたのでお願いします。

@Reputeless Reputeless moved this from Investigating to ToDo in v0.6 Roadmap Jul 10, 2023
@Reputeless Reputeless moved this from ToDo to Done in v0.6 Roadmap Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

2 participants