Skip to content

Commit 3addd7d

Browse files
authored
Merge pull request #5062 from nulano/fribidi-link
Dynamically link FriBiDi instead of Raqm
2 parents 9431d94 + c718cc6 commit 3addd7d

File tree

15 files changed

+2820
-261
lines changed

15 files changed

+2820
-261
lines changed

.github/workflows/test-windows.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,11 @@ jobs:
137137
if: steps.build-cache.outputs.cache-hit != 'true'
138138
run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
139139

140+
# Raqm dependencies
140141
- name: Build dependencies / FriBidi
141142
if: steps.build-cache.outputs.cache-hit != 'true'
142143
run: "& winbuild\\build\\build_dep_fribidi.cmd"
143144

144-
- name: Build dependencies / Raqm
145-
if: steps.build-cache.outputs.cache-hit != 'true'
146-
run: "& winbuild\\build\\build_dep_libraqm.cmd"
147-
148145
# trim ~150MB x 9
149146
- name: Optimize build cache
150147
if: steps.build-cache.outputs.cache-hit != 'true'

setup.py

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ def get_version():
2929
NAME = "Pillow"
3030
PILLOW_VERSION = get_version()
3131
FREETYPE_ROOT = None
32+
HARFBUZZ_ROOT = None
33+
FRIBIDI_ROOT = None
3234
IMAGEQUANT_ROOT = None
3335
JPEG2K_ROOT = None
3436
JPEG_ROOT = None
@@ -228,6 +230,19 @@ def _find_library_file(self, library):
228230
return ret
229231

230232

233+
def _find_include_dir(self, dirname, include):
234+
for directory in self.compiler.include_dirs:
235+
_dbg("Checking for include file %s in %s", (include, directory))
236+
if os.path.isfile(os.path.join(directory, include)):
237+
_dbg("Found %s in %s", (include, directory))
238+
return True
239+
subdir = os.path.join(directory, dirname)
240+
_dbg("Checking for include file %s in %s", (include, subdir))
241+
if os.path.isfile(os.path.join(subdir, include)):
242+
_dbg("Found %s in %s", (include, subdir))
243+
return subdir
244+
245+
231246
def _cmd_exists(cmd):
232247
return any(
233248
os.access(os.path.join(path, cmd), os.X_OK)
@@ -267,6 +282,7 @@ class feature:
267282
"jpeg",
268283
"tiff",
269284
"freetype",
285+
"raqm",
270286
"lcms",
271287
"webp",
272288
"webpmux",
@@ -276,6 +292,7 @@ class feature:
276292
]
277293

278294
required = {"jpeg", "zlib"}
295+
vendor = set()
279296

280297
def __init__(self):
281298
for f in self.features:
@@ -287,6 +304,9 @@ def require(self, feat):
287304
def want(self, feat):
288305
return getattr(self, feat) is None
289306

307+
def want_vendor(self, feat):
308+
return feat in self.vendor
309+
290310
def __iter__(self):
291311
yield from self.features
292312

@@ -296,6 +316,10 @@ def __iter__(self):
296316
build_ext.user_options
297317
+ [(f"disable-{x}", None, f"Disable support for {x}") for x in feature]
298318
+ [(f"enable-{x}", None, f"Enable support for {x}") for x in feature]
319+
+ [
320+
(f"vendor-{x}", None, f"Use vendored version of {x}")
321+
for x in ("raqm", "fribidi")
322+
]
299323
+ [
300324
("disable-platform-guessing", None, "Disable platform guessing on Linux"),
301325
("debug", None, "Debug logging"),
@@ -310,6 +334,8 @@ def initialize_options(self):
310334
for x in self.feature:
311335
setattr(self, f"disable_{x}", None)
312336
setattr(self, f"enable_{x}", None)
337+
for x in ("raqm", "fribidi"):
338+
setattr(self, f"vendor_{x}", None)
313339

314340
def finalize_options(self):
315341
build_ext.finalize_options(self)
@@ -334,18 +360,40 @@ def finalize_options(self):
334360
raise ValueError(
335361
f"Conflicting options: --enable-{x} and --disable-{x}"
336362
)
363+
if x == "freetype":
364+
_dbg("--disable-freetype implies --disable-raqm")
365+
if getattr(self, "enable_raqm"):
366+
raise ValueError(
367+
"Conflicting options: --enable-raqm and --disable-freetype"
368+
)
369+
setattr(self, "disable_raqm", True)
337370
if getattr(self, f"enable_{x}"):
338371
_dbg("Requiring %s", x)
339372
self.feature.required.add(x)
373+
if x == "raqm":
374+
_dbg("--enable-raqm implies --enable-freetype")
375+
self.feature.required.add("freetype")
376+
for x in ("raqm", "fribidi"):
377+
if getattr(self, f"vendor_{x}"):
378+
if getattr(self, "disable_raqm"):
379+
raise ValueError(
380+
f"Conflicting options: --vendor-{x} and --disable-raqm"
381+
)
382+
if x == "fribidi" and not getattr(self, "vendor_raqm"):
383+
raise ValueError(
384+
f"Conflicting options: --vendor-{x} and not --vendor-raqm"
385+
)
386+
_dbg("Using vendored version of %s", x)
387+
self.feature.vendor.add(x)
340388

341-
def _update_extension(self, name, libraries, define_macros=None, include_dirs=None):
389+
def _update_extension(self, name, libraries, define_macros=None, sources=None):
342390
for extension in self.extensions:
343391
if extension.name == name:
344392
extension.libraries += libraries
345393
if define_macros is not None:
346394
extension.define_macros += define_macros
347-
if include_dirs is not None:
348-
extension.include_dirs += include_dirs
395+
if sources is not None:
396+
extension.sources += sources
349397
if FUZZING_BUILD:
350398
extension.language = "c++"
351399
extension.extra_link_args = ["--stdlib=libc++"]
@@ -374,6 +422,8 @@ def build_extensions(self):
374422
TIFF_ROOT=("libtiff-5", "libtiff-4"),
375423
ZLIB_ROOT="zlib",
376424
FREETYPE_ROOT="freetype2",
425+
HARFBUZZ_ROOT="harfbuzz",
426+
FRIBIDI_ROOT="fribidi",
377427
LCMS_ROOT="lcms2",
378428
IMAGEQUANT_ROOT="libimagequant",
379429
).items():
@@ -659,6 +709,39 @@ def build_extensions(self):
659709
if subdir:
660710
_add_directory(self.compiler.include_dirs, subdir, 0)
661711

712+
if feature.freetype and feature.want("raqm"):
713+
if not feature.want_vendor("raqm"): # want system Raqm
714+
_dbg("Looking for Raqm")
715+
if _find_include_file(self, "raqm.h"):
716+
if _find_library_file(self, "raqm"):
717+
feature.raqm = "raqm"
718+
elif _find_library_file(self, "libraqm"):
719+
feature.raqm = "libraqm"
720+
else: # want to build Raqm from src/thirdparty
721+
_dbg("Looking for HarfBuzz")
722+
feature.harfbuzz = None
723+
hb_dir = _find_include_dir(self, "harfbuzz", "hb.h")
724+
if hb_dir:
725+
if isinstance(hb_dir, str):
726+
_add_directory(self.compiler.include_dirs, hb_dir, 0)
727+
if _find_library_file(self, "harfbuzz"):
728+
feature.harfbuzz = "harfbuzz"
729+
if feature.harfbuzz:
730+
if not feature.want_vendor("fribidi"): # want system FriBiDi
731+
_dbg("Looking for FriBiDi")
732+
feature.fribidi = None
733+
fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h")
734+
if fribidi_dir:
735+
if isinstance(fribidi_dir, str):
736+
_add_directory(
737+
self.compiler.include_dirs, fribidi_dir, 0
738+
)
739+
if _find_library_file(self, "fribidi"):
740+
feature.fribidi = "fribidi"
741+
feature.raqm = True
742+
else: # want to build FriBiDi shim from src/thirdparty
743+
feature.raqm = True
744+
662745
if feature.want("lcms"):
663746
_dbg("Looking for lcms")
664747
if _find_include_file(self, "lcms2.h"):
@@ -754,9 +837,25 @@ def build_extensions(self):
754837
# additional libraries
755838

756839
if feature.freetype:
840+
srcs = []
757841
libs = ["freetype"]
758842
defs = []
759-
self._update_extension("PIL._imagingft", libs, defs)
843+
if feature.raqm:
844+
if not feature.want_vendor("raqm"): # using system Raqm
845+
defs.append(("HAVE_RAQM", None))
846+
defs.append(("HAVE_RAQM_SYSTEM", None))
847+
libs.append(feature.raqm)
848+
else: # building Raqm from src/thirdparty
849+
defs.append(("HAVE_RAQM", None))
850+
srcs.append("src/thirdparty/raqm/raqm.c")
851+
libs.append(feature.harfbuzz)
852+
if not feature.want_vendor("fribidi"): # using system FriBiDi
853+
defs.append(("HAVE_FRIBIDI_SYSTEM", None))
854+
libs.append(feature.fribidi)
855+
else: # building FriBiDi shim from src/thirdparty
856+
srcs.append("src/thirdparty/fribidi-shim/fribidi.c")
857+
self._update_extension("PIL._imagingft", libs, defs, srcs)
858+
760859
else:
761860
self._remove_extension("PIL._imagingft")
762861

@@ -803,13 +902,20 @@ def summary_report(self, feature):
803902
print(f" [{v.strip()}")
804903
print("-" * 68)
805904

905+
raqm_extra_info = ""
906+
if feature.want_vendor("raqm"):
907+
raqm_extra_info += "bundled"
908+
if feature.want_vendor("fribidi"):
909+
raqm_extra_info += ", FriBiDi shim"
910+
806911
options = [
807912
(feature.jpeg, "JPEG"),
808913
(feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version),
809914
(feature.zlib, "ZLIB (PNG/ZIP)"),
810915
(feature.imagequant, "LIBIMAGEQUANT"),
811916
(feature.tiff, "LIBTIFF"),
812917
(feature.freetype, "FREETYPE2"),
918+
(feature.raqm, "RAQM (Text shaping)", raqm_extra_info),
813919
(feature.lcms, "LITTLECMS2"),
814920
(feature.webp, "WEBP"),
815921
(feature.webpmux, "WEBPMUX"),
@@ -819,10 +925,10 @@ def summary_report(self, feature):
819925
all = 1
820926
for option in options:
821927
if option[0]:
822-
version = ""
928+
extra_info = ""
823929
if len(option) >= 3 and option[2]:
824-
version = f" ({option[2]})"
825-
print(f"--- {option[1]} support available{version}")
930+
extra_info = f" ({option[2]})"
931+
print(f"--- {option[1]} support available{extra_info}")
826932
else:
827933
print(f"*** {option[1]} support not available")
828934
all = 0

src/PIL/features.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ def get_supported_codecs():
118118
"webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
119119
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
120120
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
121+
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
122+
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
121123
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
122124
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
123125
"xcb": ("PIL._imaging", "HAVE_XCB", None),
@@ -274,6 +276,11 @@ def pilinfo(out=None, supported_formats=True):
274276
# this check is also in src/_imagingcms.c:setup_module()
275277
version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
276278
t = "compiled for" if version_static else "loaded"
279+
if name == "raqm":
280+
for f in ("fribidi", "harfbuzz"):
281+
v2 = version_feature(f)
282+
if v2 is not None:
283+
v += f", {f} {v2}"
277284
print("---", feature, "support ok,", t, v, file=out)
278285
else:
279286
print("---", feature, "support ok", file=out)

0 commit comments

Comments
 (0)