You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Bug] Blend2D font resolver throws "Cannot convert character sequence" on Windows when LOCALAPPDATA contains non-ASCII characters (breaks default docling PDF backend) #354
On a Windows machine whose user profile path contains non-ASCII characters (Korean in the downstream report, but any character outside ASCII in a non-UTF-8 ANSI code page will do), constructing _threaded_pdf_renderer throws:
RuntimeError: filesystem error: Cannot convert character sequence: Illegal byte sequence
Since docling 2.1xx defaults PdfFormatOption.backend to ThreadedDoclingParseDocumentBackend, this breaks every PDF conversion for such users, regardless of where the PDF itself lives. The non-threaded render_image path hits the same code lazily on the first page render (blend2d_font_resolver::default_resolver()), so it is not specific to the threaded API.
Downstream report with full reproduction: opendataloader-project/opendataloader-pdf#725. The reporter worked around it by switching docling to DoclingParseDocumentBackend, which happens to render page images with pypdfium2 and so never constructs the font resolver.
Cause
src/render/blend2d_font_resolver.h:
inline std::optional<std::string> blend2d_font_resolver::getenv_string(constchar* name)
{
constchar* value = std::getenv(name); // line 621
...
}
inlinevoidblend2d_font_resolver::append_env_path(...)
{
auto value = getenv_string(env_name);
if (not value.has_value()) { return; }
std::filesystem::path path(*value); // line 633
...
}
// system_font_directories(), line 646append_env_path(dirs, "LOCALAPPDATA", std::filesystem::path("Microsoft") / "Windows" / "Fonts");
Two facts collide:
The Windows amd64 wheel is built with MinGW GCC (.github/workflows/wheels.yml puts C:\msys64\mingw64\bin on PATH; CMakeLists.txt has a MinGW branch). libstdc++ on Windows treats every narrow char path string as UTF-8 and throws filesystem_error("Cannot convert character sequence", errc::illegal_byte_sequence) when the bytes are not valid UTF-8 (bits/fs_path.h, __detail::__wstr_from_utf8, path::_Codecvt<wchar_t> = codecvt_utf8_utf16).
The C runtime's getenv returns the variable in the process ANSI code page (CP949 on a Korean-locale machine, cp1252 on most Western ones). LOCALAPPDATA is C:\Users\<username>\AppData\Local, so a non-ASCII username produces bytes that are not UTF-8.
The scan runs eagerly in the threaded renderer constructor (src/pybind/docling_threaded_renderer.h:55, font_resolver_->warm() → build_font_index() → system_font_directories()), which is why the exception surfaces before any page is touched. There is no try/catch around it, so one unconvertible environment variable takes the whole renderer down.
The DOCLING_PARSE_FALLBACK_FONT, DOCLING_PARSE_ARABIC_FALLBACK_FONT and DOCLING_PARSE_CJK_FALLBACK_FONT overrides go through the same getenv_string and have the same problem.
This is not the file-open path: both docling_parser::load_document and docling_threaded_base::load_document already convert the UTF-8 filename to a wide path, and document.h uses std::u8string, so a PDF under C:\Users\한글사용자\... loads fine (the downstream reporter confirmed that too).
Steps to reproduce
Windows amd64, system locale set to Korean (or any non-UTF-8 ANSI code page), logged in as a user whose name contains a character outside ASCII. The "Beta: Use Unicode UTF-8 for worldwide language support" option must be off (it is off by default).
(or just point it at a directory whose name contains é on a cp1252 machine).
Suggested fix
On _WIN32, read environment variables with _wgetenv (or GetEnvironmentVariableW) and build the path from std::wstring, so the ANSI code page never enters the conversion. Alternatively convert with MultiByteToWideChar(CP_ACP, ...).
Wrap the directory scan in build_font_index() in a try/catch so a bad environment variable degrades to "no system fonts found" instead of aborting the renderer.
Same class of bug on the MSVC (arm64) build, in the other direction: src/pybind/docling_resources.h:47 builds std::filesystem::path __init__path(filename) from the UTF-8 const char* that PyUnicode_AsUTF8 returns, which MSVC interprets as ANSI. That is DoclingPdfParser fails to load resources if user path contains special characters (e.g. á) on Windows #115 (profile path with á, still open). std::u8string there, as document.h already does, fixes both toolchains.
Happy to open a PR for 1 to 3 if that is welcome.
Version
docling-parse: 7.16.0 (downstream report); code unchanged on main at v7.21.0
docling: 2.126.0
Python: 3.12, Windows amd64, Korean system locale
Bug
On a Windows machine whose user profile path contains non-ASCII characters (Korean in the downstream report, but any character outside ASCII in a non-UTF-8 ANSI code page will do), constructing
_threaded_pdf_rendererthrows:Since docling 2.1xx defaults
PdfFormatOption.backendtoThreadedDoclingParseDocumentBackend, this breaks every PDF conversion for such users, regardless of where the PDF itself lives. The non-threadedrender_imagepath hits the same code lazily on the first page render (blend2d_font_resolver::default_resolver()), so it is not specific to the threaded API.Downstream report with full reproduction: opendataloader-project/opendataloader-pdf#725. The reporter worked around it by switching docling to
DoclingParseDocumentBackend, which happens to render page images with pypdfium2 and so never constructs the font resolver.Cause
src/render/blend2d_font_resolver.h:Two facts collide:
.github/workflows/wheels.ymlputsC:\msys64\mingw64\binon PATH;CMakeLists.txthas a MinGW branch). libstdc++ on Windows treats every narrowcharpath string as UTF-8 and throwsfilesystem_error("Cannot convert character sequence", errc::illegal_byte_sequence)when the bytes are not valid UTF-8 (bits/fs_path.h,__detail::__wstr_from_utf8,path::_Codecvt<wchar_t>=codecvt_utf8_utf16).getenvreturns the variable in the process ANSI code page (CP949 on a Korean-locale machine, cp1252 on most Western ones).LOCALAPPDATAisC:\Users\<username>\AppData\Local, so a non-ASCII username produces bytes that are not UTF-8.The scan runs eagerly in the threaded renderer constructor (
src/pybind/docling_threaded_renderer.h:55,font_resolver_->warm()→build_font_index()→system_font_directories()), which is why the exception surfaces before any page is touched. There is no try/catch around it, so one unconvertible environment variable takes the whole renderer down.The
DOCLING_PARSE_FALLBACK_FONT,DOCLING_PARSE_ARABIC_FALLBACK_FONTandDOCLING_PARSE_CJK_FALLBACK_FONToverrides go through the samegetenv_stringand have the same problem.This is not the file-open path: both
docling_parser::load_documentanddocling_threaded_base::load_documentalready convert the UTF-8 filename to a wide path, anddocument.husesstd::u8string, so a PDF underC:\Users\한글사용자\...loads fine (the downstream reporter confirmed that too).Steps to reproduce
Windows amd64, system locale set to Korean (or any non-UTF-8 ANSI code page), logged in as a user whose name contains a character outside ASCII. The "Beta: Use Unicode UTF-8 for worldwide language support" option must be off (it is off by default).
Same result from
DoclingPdfParseron the firstPdfPage.render_as_image(...)call.A cheaper reproduction on any Windows amd64 box is to set
LOCALAPPDATAto a value that is not valid UTF-8 before importing:(or just point it at a directory whose name contains
éon a cp1252 machine).Suggested fix
_WIN32, read environment variables with_wgetenv(orGetEnvironmentVariableW) and build the path fromstd::wstring, so the ANSI code page never enters the conversion. Alternatively convert withMultiByteToWideChar(CP_ACP, ...).build_font_index()in a try/catch so a bad environment variable degrades to "no system fonts found" instead of aborting the renderer.src/pybind/docling_resources.h:47buildsstd::filesystem::path __init__path(filename)from the UTF-8const char*thatPyUnicode_AsUTF8returns, which MSVC interprets as ANSI. That is DoclingPdfParser fails to load resources if user path contains special characters (e.g. á) on Windows #115 (profile path withá, still open).std::u8stringthere, asdocument.halready does, fixes both toolchains.Happy to open a PR for 1 to 3 if that is welcome.
Version