Merge branch 'main' into deprecate-getsize

This commit is contained in:
Andrew Murray 2022-06-30 20:37:37 +10:00
commit 0d91d13a6e
29 changed files with 159 additions and 74 deletions

View File

@ -5,6 +5,36 @@ Changelog (Pillow)
9.2.0 (unreleased) 9.2.0 (unreleased)
------------------ ------------------
- Fixed null check for fribidi_version_info in FriBiDi shim #6376
[nulano]
- Added GIF decompression bomb check #6402
[radarhere]
- Handle PCF fonts files with less than 256 characters #6386
[dawidcrivelli, radarhere]
- Improved GIF optimize condition #6378
[raygard, radarhere]
- Reverted to __array_interface__ with the release of NumPy 1.23 #6394
[radarhere]
- Pad PCX palette to 768 bytes when saving #6391
[radarhere]
- Fixed bug with rounding pixels to palette colors #6377
[btrekkie, radarhere]
- Use gnome-screenshot on Linux if available #6361
[radarhere, nulano]
- Fixed loading L mode BMP RLE8 images #6384
[radarhere]
- Fixed incorrect operator in ImageCms error #6370
[LostBenjamin, hugovk, radarhere]
- Limit FPX tile size to avoid extending outside image #6368 - Limit FPX tile size to avoid extending outside image #6368
[radarhere] [radarhere]

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -61,6 +61,11 @@ class TestDecompressionBomb:
with Image.open("Tests/images/decompression_bomb.gif"): with Image.open("Tests/images/decompression_bomb.gif"):
pass pass
def test_exception_gif_extents(self):
with Image.open("Tests/images/decompression_bomb_extents.gif") as im:
with pytest.raises(Image.DecompressionBombError):
im.seek(1)
def test_exception_bmp(self): def test_exception_bmp(self):
with pytest.raises(Image.DecompressionBombError): with pytest.raises(Image.DecompressionBombError):
with Image.open("Tests/images/bmp/b/reallybig.bmp"): with Image.open("Tests/images/bmp/b/reallybig.bmp"):

View File

@ -134,6 +134,9 @@ def test_rle8():
with Image.open("Tests/images/hopper_rle8.bmp") as im: with Image.open("Tests/images/hopper_rle8.bmp") as im:
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12) assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
with Image.open("Tests/images/hopper_rle8_greyscale.bmp") as im:
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
# This test image has been manually hexedited # This test image has been manually hexedited
# to have rows with too much data # to have rows with too much data
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im: with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:

View File

@ -158,6 +158,9 @@ def test_optimize_correctness():
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
# These do optimize the palette # These do optimize the palette
check(256, 511, 256)
check(255, 511, 255)
check(129, 511, 129)
check(128, 511, 128) check(128, 511, 128)
check(64, 511, 64) check(64, 511, 64)
check(4, 511, 4) check(4, 511, 4)
@ -167,11 +170,6 @@ def test_optimize_correctness():
check(64, 513, 256) check(64, 513, 256)
check(4, 513, 256) check(4, 513, 256)
# Other limits that don't optimize the palette
check(129, 511, 256)
check(255, 511, 256)
check(256, 511, 256)
def test_optimize_full_l(): def test_optimize_full_l():
im = Image.frombytes("L", (16, 16), bytes(range(256))) im = Image.frombytes("L", (16, 16), bytes(range(256)))
@ -180,6 +178,19 @@ def test_optimize_full_l():
assert im.mode == "L" assert im.mode == "L"
def test_optimize_if_palette_can_be_reduced_by_half():
with Image.open("Tests/images/test.colors.gif") as im:
# Reduce dimensions because original is too big for _get_optimize()
im = im.resize((591, 443))
im_rgb = im.convert("RGB")
for (optimize, colors) in ((False, 256), (True, 8)):
out = BytesIO()
im_rgb.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded:
assert len(reloaded.palette.palette) // 3 == colors
def test_roundtrip(tmp_path): def test_roundtrip(tmp_path):
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im = hopper() im = hopper()
@ -982,8 +993,8 @@ def test_append_images(tmp_path):
def test_transparent_optimize(tmp_path): def test_transparent_optimize(tmp_path):
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses # From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
# transparency. # transparency.
# Need a palette that isn't using the 0 color, and one that's > 128 items where the # Need a palette that isn't using the 0 color,
# transparent color is actually the top palette entry to trigger the bug. # where the transparent color is actually the top palette entry to trigger the bug.
data = bytes(range(1, 254)) data = bytes(range(1, 254))
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
@ -993,10 +1004,10 @@ def test_transparent_optimize(tmp_path):
im.putpalette(palette) im.putpalette(palette)
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im.save(out, transparency=253) im.save(out, transparency=im.getpixel((252, 0)))
with Image.open(out) as reloaded:
assert reloaded.info["transparency"] == 253 with Image.open(out) as reloaded:
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
def test_rgb_transparency(tmp_path): def test_rgb_transparency(tmp_path):

View File

@ -20,6 +20,11 @@ def test_sanity(tmp_path):
for mode in ("1", "L", "P", "RGB"): for mode in ("1", "L", "P", "RGB"):
_roundtrip(tmp_path, hopper(mode)) _roundtrip(tmp_path, hopper(mode))
# Test a palette with less than 256 colors
im = Image.new("P", (1, 1))
im.putpalette((255, 0, 0))
_roundtrip(tmp_path, im)
# Test an unsupported mode # Test an unsupported mode
f = str(tmp_path / "temp.pcx") f = str(tmp_path / "temp.pcx")
im = hopper("RGBA") im = hopper("RGBA")

View File

@ -49,6 +49,14 @@ def test_sanity(request, tmp_path):
save_font(request, tmp_path) save_font(request, tmp_path)
def test_less_than_256_characters():
with open("Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf", "rb") as test_file:
font = PcfFontFile.PcfFontFile(test_file)
assert isinstance(font, FontFile.FontFile)
# check the number of characters in the font
assert len([_f for _f in font.glyph if _f]) == 127
def test_invalid_file(): def test_invalid_file():
with open("Tests/images/flower.jpg", "rb") as fp: with open("Tests/images/flower.jpg", "rb") as fp:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):

View File

@ -1,4 +1,5 @@
import pytest import pytest
from packaging.version import parse as parse_version
from PIL import Image from PIL import Image
@ -34,6 +35,7 @@ def test_toarray():
test_with_dtype(numpy.float64) test_with_dtype(numpy.float64)
test_with_dtype(numpy.uint8) test_with_dtype(numpy.uint8)
if parse_version(numpy.__version__) >= parse_version("1.23"):
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated: with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
with pytest.raises(OSError): with pytest.raises(OSError):
numpy.array(im_truncated) numpy.array(im_truncated)

View File

@ -9,7 +9,7 @@ def test_entropy():
assert round(abs(entropy("L") - 7.063008716585465), 7) == 0 assert round(abs(entropy("L") - 7.063008716585465), 7) == 0
assert round(abs(entropy("I") - 7.063008716585465), 7) == 0 assert round(abs(entropy("I") - 7.063008716585465), 7) == 0
assert round(abs(entropy("F") - 7.063008716585465), 7) == 0 assert round(abs(entropy("F") - 7.063008716585465), 7) == 0
assert round(abs(entropy("P") - 5.0530452472519745), 7) == 0 assert round(abs(entropy("P") - 5.082506854662517), 7) == 0
assert round(abs(entropy("RGB") - 8.821286587714319), 7) == 0 assert round(abs(entropy("RGB") - 8.821286587714319), 7) == 0
assert round(abs(entropy("RGBA") - 7.42724306524488), 7) == 0 assert round(abs(entropy("RGBA") - 7.42724306524488), 7) == 0
assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0 assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0

View File

@ -16,7 +16,7 @@ def test_getcolors():
assert getcolors("L") == 255 assert getcolors("L") == 255
assert getcolors("I") == 255 assert getcolors("I") == 255
assert getcolors("F") == 255 assert getcolors("F") == 255
assert getcolors("P") == 90 # fixed palette assert getcolors("P") == 96 # fixed palette
assert getcolors("RGB") is None assert getcolors("RGB") is None
assert getcolors("RGBA") is None assert getcolors("RGBA") is None
assert getcolors("CMYK") is None assert getcolors("CMYK") is None

View File

@ -10,7 +10,7 @@ def test_histogram():
assert histogram("L") == (256, 0, 662) assert histogram("L") == (256, 0, 662)
assert histogram("I") == (256, 0, 662) assert histogram("I") == (256, 0, 662)
assert histogram("F") == (256, 0, 662) assert histogram("F") == (256, 0, 662)
assert histogram("P") == (256, 0, 1871) assert histogram("P") == (256, 0, 1551)
assert histogram("RGB") == (768, 4, 675) assert histogram("RGB") == (768, 4, 675)
assert histogram("RGBA") == (1024, 0, 16384) assert histogram("RGBA") == (1024, 0, 16384)
assert histogram("CMYK") == (1024, 0, 16384) assert histogram("CMYK") == (1024, 0, 16384)

View File

@ -65,6 +65,22 @@ def test_quantize_no_dither():
assert converted.palette.palette == palette.palette.palette assert converted.palette.palette == palette.palette.palette
def test_quantize_no_dither2():
im = Image.new("RGB", (9, 1))
im.putdata(list((p,) * 3 for p in range(0, 36, 4)))
palette = Image.new("P", (1, 1))
data = (0, 0, 0, 32, 32, 32)
palette.putpalette(data)
quantized = im.quantize(dither=Image.Dither.NONE, palette=palette)
assert tuple(quantized.palette.palette) == data
px = quantized.load()
for x in range(9):
assert px[x, 0] == (0 if x < 5 else 1)
def test_quantize_dither_diff(): def test_quantize_dither_diff():
image = hopper() image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette: with Image.open("Tests/images/caption_6_33_22.png") as palette:

View File

@ -94,4 +94,3 @@ Indices and tables
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
* :ref:`search`

View File

@ -15,7 +15,10 @@ or the clipboard to a PIL image memory.
returned as an "RGBA" on macOS, or an "RGB" image otherwise. returned as an "RGBA" on macOS, or an "RGB" image otherwise.
If the bounding box is omitted, the entire screen is copied. If the bounding box is omitted, the entire screen is copied.
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux (X11)) On Linux, if ``xdisplay`` is ``None`` then ``gnome-screenshot`` will be used if it
is installed. To capture the default X11 display instead, pass ``xdisplay=""``.
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
:param bbox: What region to copy. Default is the entire screen. :param bbox: What region to copy. Default is the entire screen.
Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used. Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used.

View File

@ -1,12 +1,6 @@
9.2.0 9.2.0
----- -----
Backwards Incompatible Changes
==============================
TODO
^^^^
Deprecations Deprecations
============ ============
@ -46,14 +40,6 @@ Image.coerce_e
This undocumented method has been deprecated and will be removed in Pillow 10 This undocumented method has been deprecated and will be removed in Pillow 10
(2023-07-01). (2023-07-01).
API Changes
===========
TODO
^^^^
TODO
API Additions API Additions
============= =============
@ -68,15 +54,14 @@ The image's palette mode will become "RGBA", and "transparency" will be removed
Security Security
======== ========
TODO An additional decompression bomb check has been added for the GIF format.
^^^^
TODO
Other Changes Other Changes
============= =============
TODO Using gnome-screenshot on Linux
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO In :py:meth:`~PIL.ImageGrab.grab` on Linux, if ``xdisplay`` is ``None`` then
``gnome-screenshot`` will be used to capture the display if it is installed. To capture
the default X11 display instead, pass ``xdisplay=""``.

View File

@ -321,7 +321,8 @@ class BmpRleDecoder(ImageFile.PyDecoder):
# align to 16-bit word boundary # align to 16-bit word boundary
if self.fd.tell() % 2 != 0: if self.fd.tell() % 2 != 0:
self.fd.seek(1, os.SEEK_CUR) self.fd.seek(1, os.SEEK_CUR)
self.set_as_raw(bytes(data), ("P", 0, self.args[-1])) rawmode = "L" if self.mode == "L" else "P"
self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
return -1, 0 return -1, 0

View File

@ -265,6 +265,7 @@ class GifImageFile(ImageFile.ImageFile):
x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6) x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
if (x1 > self.size[0] or y1 > self.size[1]) and update_image: if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
self._size = max(x1, self.size[0]), max(y1, self.size[1]) self._size = max(x1, self.size[0]), max(y1, self.size[1])
Image._decompression_bomb_check(self._size)
frame_dispose_extent = x0, y0, x1, y1 frame_dispose_extent = x0, y0, x1, y1
flags = s[8] flags = s[8]
@ -824,9 +825,18 @@ def _get_optimize(im, info):
if count: if count:
used_palette_colors.append(i) used_palette_colors.append(i)
if optimise or ( if optimise or max(used_palette_colors) >= len(used_palette_colors):
len(used_palette_colors) <= 128 return used_palette_colors
and max(used_palette_colors) > len(used_palette_colors)
num_palette_colors = len(im.palette.palette) // Image.getmodebands(
im.palette.mode
)
current_palette_size = 1 << (num_palette_colors - 1).bit_length()
if (
# check that the palette would become smaller when saved
len(used_palette_colors) <= current_palette_size // 2
# check that the palette is not already the smallest possible size
and current_palette_size > 2
): ):
return used_palette_colors return used_palette_colors

View File

@ -671,14 +671,9 @@ class Image:
raise ValueError("Could not save to PNG for display") from e raise ValueError("Could not save to PNG for display") from e
return b.getvalue() return b.getvalue()
class _ArrayData: @property
def __init__(self, new): def __array_interface__(self):
self.__array_interface__ = new
def __array__(self, dtype=None):
# numpy array interface support # numpy array interface support
import numpy as np
new = {} new = {}
shape, typestr = _conv_type_shape(self) shape, typestr = _conv_type_shape(self)
new["shape"] = shape new["shape"] = shape
@ -690,8 +685,7 @@ class Image:
new["data"] = self.tobytes("raw", "L") new["data"] = self.tobytes("raw", "L")
else: else:
new["data"] = self.tobytes() new["data"] = self.tobytes()
return new
return np.array(self._ArrayData(new), dtype)
def __getstate__(self): def __getstate__(self):
return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()] return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()]

View File

@ -15,15 +15,14 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import os
import shutil
import subprocess
import sys import sys
import tempfile
from . import Image from . import Image
if sys.platform == "darwin":
import os
import subprocess
import tempfile
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None): def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
if xdisplay is None: if xdisplay is None:
@ -62,6 +61,18 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
left, top, right, bottom = bbox left, top, right, bottom = bbox
im = im.crop((left - x0, top - y0, right - x0, bottom - y0)) im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
return im return im
elif shutil.which("gnome-screenshot"):
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
subprocess.call(["gnome-screenshot", "-f", filepath])
im = Image.open(filepath)
im.load()
os.unlink(filepath)
if bbox:
im_cropped = im.crop(bbox)
im.close()
return im_cropped
return im
# use xdisplay=None for default display on non-win32/macOS systems # use xdisplay=None for default display on non-win32/macOS systems
if not Image.core.HAVE_XCB: if not Image.core.HAVE_XCB:
raise OSError("Pillow was built without XCB support") raise OSError("Pillow was built without XCB support")

View File

@ -84,8 +84,7 @@ class PcfFontFile(FontFile.FontFile):
# #
# create glyph structure # create glyph structure
for ch in range(256): for ch, ix in enumerate(encoding):
ix = encoding[ch]
if ix is not None: if ix is not None:
x, y, l, r, w, a, d, f = metrics[ix] x, y, l, r, w, a, d, f = metrics[ix]
glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix] glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix]
@ -219,10 +218,6 @@ class PcfFontFile(FontFile.FontFile):
return bitmaps return bitmaps
def _load_encoding(self): def _load_encoding(self):
# map character code to bitmap index
encoding = [None] * 256
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS) fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
first_col, last_col = i16(fp.read(2)), i16(fp.read(2)) first_col, last_col = i16(fp.read(2)), i16(fp.read(2))
@ -232,6 +227,9 @@ class PcfFontFile(FontFile.FontFile):
nencoding = (last_col - first_col + 1) * (last_row - first_row + 1) nencoding = (last_col - first_col + 1) * (last_row - first_row + 1)
# map character code to bitmap index
encoding = [None] * min(256, nencoding)
encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)] encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)]
for i in range(first_col, len(encoding)): for i in range(first_col, len(encoding)):

View File

@ -198,7 +198,9 @@ def _save(im, fp, filename):
if im.mode == "P": if im.mode == "P":
# colour palette # colour palette
fp.write(o8(12)) fp.write(o8(12))
fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes palette = im.im.getpalette("RGB", "RGB")
palette += b"\x00" * (768 - len(palette))
fp.write(palette) # 768 bytes
elif im.mode == "L": elif im.mode == "L":
# greyscale palette # greyscale palette
fp.write(o8(12)) fp.write(o8(12))

View File

@ -200,15 +200,15 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) {
/* Find min and max distances to any point in the box */ /* Find min and max distances to any point in the box */
r = palette->palette[i * 4 + 0]; r = palette->palette[i * 4 + 0];
tmin = (r < r0) ? RDIST(r, r1) : (r > r1) ? RDIST(r, r0) : 0; tmin = (r < r0) ? RDIST(r, r0) : (r > r1) ? RDIST(r, r1) : 0;
tmax = (r <= rc) ? RDIST(r, r1) : RDIST(r, r0); tmax = (r <= rc) ? RDIST(r, r1) : RDIST(r, r0);
g = palette->palette[i * 4 + 1]; g = palette->palette[i * 4 + 1];
tmin += (g < g0) ? GDIST(g, g1) : (g > g1) ? GDIST(g, g0) : 0; tmin += (g < g0) ? GDIST(g, g0) : (g > g1) ? GDIST(g, g1) : 0;
tmax += (g <= gc) ? GDIST(g, g1) : GDIST(g, g0); tmax += (g <= gc) ? GDIST(g, g1) : GDIST(g, g0);
b = palette->palette[i * 4 + 2]; b = palette->palette[i * 4 + 2];
tmin += (b < b0) ? BDIST(b, b1) : (b > b1) ? BDIST(b, b0) : 0; tmin += (b < b0) ? BDIST(b, b0) : (b > b1) ? BDIST(b, b1) : 0;
tmax += (b <= bc) ? BDIST(b, b1) : BDIST(b, b0); tmax += (b <= bc) ? BDIST(b, b1) : BDIST(b, b0);
dmin[i] = tmin; dmin[i] = tmin;

View File

@ -33,6 +33,7 @@ static void fribidi_get_bracket_types_compat(
int load_fribidi(void) { int load_fribidi(void) {
int error = 0; int error = 0;
const char **p_fribidi_version_info = 0;
p_fribidi = 0; p_fribidi = 0;
@ -87,20 +88,21 @@ int load_fribidi(void) {
LOAD_FUNCTION(fribidi_get_par_embedding_levels); LOAD_FUNCTION(fribidi_get_par_embedding_levels);
#ifndef _WIN32 #ifndef _WIN32
fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); p_fribidi_version_info = (const char**)dlsym(p_fribidi, "fribidi_version_info");
if (error || (fribidi_version_info == 0)) { if (error || (p_fribidi_version_info == 0) || (*p_fribidi_version_info == 0)) {
dlclose(p_fribidi); dlclose(p_fribidi);
p_fribidi = 0; p_fribidi = 0;
return 2; return 2;
} }
#else #else
fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); p_fribidi_version_info = (const char**)GetProcAddress(p_fribidi, "fribidi_version_info");
if (error || (fribidi_version_info == 0)) { if (error || (p_fribidi_version_info == 0) || (*p_fribidi_version_info == 0)) {
FreeLibrary(p_fribidi); FreeLibrary(p_fribidi);
p_fribidi = 0; p_fribidi = 0;
return 2; return 2;
} }
#endif #endif
fribidi_version_info = *p_fribidi_version_info;
return 0; return 0;
} }

View File

@ -281,9 +281,9 @@ deps = {
"libs": [r"imagequant.lib"], "libs": [r"imagequant.lib"],
}, },
"harfbuzz": { "harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.3.0.zip", "url": "https://github.com/harfbuzz/harfbuzz/archive/4.4.1.zip",
"filename": "harfbuzz-4.3.0.zip", "filename": "harfbuzz-4.4.1.zip",
"dir": "harfbuzz-4.3.0", "dir": "harfbuzz-4.4.1",
"build": [ "build": [
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
cmd_nmake(target="clean"), cmd_nmake(target="clean"),