from copy import deepcopy
from fontTools.misc.roundTools import otRound
from fontTools.misc.textTools import num2binary
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.pens.boundsPen import BoundsPen
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.O_S_2f_2 import table_O_S_2f_2
from foundrytools.constants import (
MAX_US_WEIGHT_CLASS,
MAX_US_WIDTH_CLASS,
MIN_US_WEIGHT_CLASS,
MIN_US_WIDTH_CLASS,
T_CMAP,
T_OS_2,
)
from foundrytools.core.tables.default import DefaultTbl
from foundrytools.lib.unicode import (
OS_2_UNICODE_RANGES,
check_block_support,
count_block_codepoints,
)
from foundrytools.utils.bits_tools import is_nth_bit_set
ITALIC_BIT = 0
UNDERSCORE_BIT = 1
NEGATIVE_BIT = 2
OUTLINED_BIT = 3
STRIKEOUT_BIT = 4
BOLD_BIT = 5
REGULAR_BIT = 6
USE_TYPO_METRICS_BIT = 7
WWS_BIT = 8
OBLIQUE_BIT = 9
NO_SUBSETTING_BIT = 8
BITMAP_EMBED_ONLY_BIT = 9
MIN_OS2_VERSION = 0
MAX_OS2_VERSION = 5
[docs]
class InvalidOS2VersionError(Exception):
"""
Exception raised when trying to access a field that is not defined in the current OS/2 table
version.
"""
[docs]
class FsSelection:
"""A wrapper class for the ``fsSelection`` field of the ``OS/2`` table."""
def __init__(self, os_2_table: "OS2Table"):
"""
Initializes the ``fsSelection`` field of the ``OS/2`` table.
:param os_2_table: The ``OS/2`` table.
:type os_2_table: OS2Table
"""
self.os_2_table = os_2_table
def __repr__(self) -> str:
return f"fsSelection({num2binary(self.os_2_table.table.fsSelection)})"
@property
def italic(self) -> bool:
"""
A property with getter and setter for bit 0 (ITALIC) in the ``OS/2.fsSelection`` field.
:return: True if bit 0 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, ITALIC_BIT)
@italic.setter
def italic(self, value: bool) -> None:
"""Sets bit 0 (ITALIC) of the ``OS/2.fsSelection`` field."""
self.os_2_table.set_bit(field_name="fsSelection", pos=ITALIC_BIT, value=value)
@property
def underscore(self) -> bool:
"""
A property with getter and setter for bit 1 (UNDERSCORE) in the ``OS/2.fsSelection`` field.
:return: True if bit 1 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, UNDERSCORE_BIT)
@underscore.setter
def underscore(self, value: bool) -> None:
"""
Sets bit 1 (UNDERSCORE) of the ``OS/2.fsSelection`` field.
:param value: The value to set.
:type value: bool
"""
self.os_2_table.set_bit(field_name="fsSelection", pos=UNDERSCORE_BIT, value=value)
@property
def negative(self) -> bool:
"""
A property with getter and setter for bit 2 (NEGATIVE) in the ``OS/2.fsSelection`` field.
:return: True if bit 2 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, NEGATIVE_BIT)
@negative.setter
def negative(self, value: bool) -> None:
"""Sets bit 2 (NEGATIVE) of the ``OS/2.fsSelection`` field."""
self.os_2_table.set_bit(field_name="fsSelection", pos=NEGATIVE_BIT, value=value)
@property
def outlined(self) -> bool:
"""
A property with getter and setter for bit 3 (OUTLINED) in the ``OS/2.fsSelection`` field.
:return: True if bit 3 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, OUTLINED_BIT)
@outlined.setter
def outlined(self, value: bool) -> None:
"""Sets bit 3 (OUTLINED) of the ``OS/2.fsSelection`` field."""
self.os_2_table.set_bit(field_name="fsSelection", pos=OUTLINED_BIT, value=value)
@property
def strikeout(self) -> bool:
"""
A property with getter and setter for bit 4 (STRIKEOUT) in the ``OS/2.fsSelection`` field.
:return: True if bit 4 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, STRIKEOUT_BIT)
@strikeout.setter
def strikeout(self, value: bool) -> None:
"""Sets bit 4 (STRIKEOUT) of the ``OS/2.fsSelection`` field."""
self.os_2_table.set_bit(field_name="fsSelection", pos=STRIKEOUT_BIT, value=value)
@property
def bold(self) -> bool:
"""
A property with getter and setter for bit 5 (BOLD) in the ``OS/2.fsSelection`` field.
:return: True if bit 5 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, BOLD_BIT)
@bold.setter
def bold(self, value: bool) -> None:
"""Sets the bit 5 (BOLD) of the ``OS/2.fsSelection`` field."""
self.os_2_table.set_bit(field_name="fsSelection", pos=BOLD_BIT, value=value)
@property
def regular(self) -> bool:
"""
A property with getter and setter for bit 6 (REGULAR) in the ``OS/2.fsSelection`` field.
:return: True if bit 6 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, REGULAR_BIT)
@regular.setter
def regular(self, value: bool) -> None:
"""Sets bit 6 (REGULAR) of the ``OS/2.fsSelection`` field."""
self.os_2_table.set_bit(field_name="fsSelection", pos=REGULAR_BIT, value=value)
@property
def use_typo_metrics(self) -> bool:
"""
A property with getter and setter for bit 7 (USE_TYPO_METRICS) in the ``OS/2.fsSelection``
:return: True if bit 7 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, USE_TYPO_METRICS_BIT)
@use_typo_metrics.setter
def use_typo_metrics(self, value: bool) -> None:
"""Sets bit 7 (USE_TYPO_METRICS) of the ``OS/2.fsSelection`` field."""
if self.os_2_table.version < 4 and value is True:
raise InvalidOS2VersionError(
"fsSelection bit 7 (USE_TYPO_METRICS) is only defined in OS/2 table versions 4 and "
"up."
)
self.os_2_table.set_bit(field_name="fsSelection", pos=USE_TYPO_METRICS_BIT, value=value)
@property
def wws_consistent(self) -> bool:
"""
A property with getter and setter for bit 8 (WWS) in the ``OS/2.fsSelection`` field.
:return: True if bit 8 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, WWS_BIT)
@wws_consistent.setter
def wws_consistent(self, value: bool) -> None:
"""Sets bit 8 (WWS) of the ``OS/2.fsSelection`` field."""
if self.os_2_table.version < 4 and value is True:
raise InvalidOS2VersionError(
"fsSelection bit 8 (WWS) is only defined in OS/2 table versions 4 and up."
)
self.os_2_table.set_bit(field_name="fsSelection", pos=WWS_BIT, value=value)
@property
def oblique(self) -> bool:
"""
A property with getter and setter for bit 9 (OBLIQUE) in the ``OS/2.fsSelection`` field.
:return: True if bit 9 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.os_2_table.table.fsSelection, OBLIQUE_BIT)
@oblique.setter
def oblique(self, value: bool) -> None:
"""Sets the bit 9 (OBLIQUE) of the ``OS/2.fsSelection`` field."""
if self.os_2_table.version < 4:
raise InvalidOS2VersionError(
"fsSelection bit 9 (OBLIQUE) is only defined in OS/2 table versions 4 and up."
)
self.os_2_table.set_bit(field_name="fsSelection", pos=OBLIQUE_BIT, value=value)
[docs]
class OS2Table(DefaultTbl): # pylint: disable=too-many-public-methods, too-many-instance-attributes
"""This class extends the fontTools ``OS/2`` table."""
def __init__(self, ttfont: TTFont):
"""
Initializes the ``OS/2`` table handler.
:param ttfont: The ``TTFont`` object.
:type ttfont: TTFont
"""
super().__init__(ttfont=ttfont, table_tag=T_OS_2)
self.fs_selection = FsSelection(self)
self._copy = deepcopy(self.table)
@property
def table(self) -> table_O_S_2f_2:
"""
The wrapped ``table_O_S_2f_2`` table object.
"""
return self._table
@table.setter
def table(self, value: table_O_S_2f_2) -> None:
"""
Wraps a new ``table_O_S_2f_2`` object.
"""
self._table = value
@property
def is_modified(self) -> bool:
"""
A property that returns True if the OS/2 table has been modified.
:return: True if the OS/2 table has been modified, False otherwise.
:rtype: bool
"""
return self._copy.compile(self.ttfont) != self.table.compile(self.ttfont)
@property
def version(self) -> int:
"""
A property with getter and setter for the ``OS/2.version`` field.
Valid values are between 0 and 5.
:return: The ``OS/2.version`` value.
:rtype: int
"""
return self.table.version
@version.setter
def version(self, value: int) -> None:
"""
Sets the ``OS/2.version`` value.
:param value: The value to set.
:type value: int
:raises: ValueError: If the value is not between 0 and 5.
"""
if value < MIN_OS2_VERSION or value > MAX_OS2_VERSION:
raise ValueError(
f"Invalid value for version: {value}. "
f"Expected a value between {MIN_OS2_VERSION} and {MAX_OS2_VERSION}."
)
self.table.version = value
@property
def weight_class(self) -> int:
"""
A property with getter and setter for the ``OS/2.usWeightClass`` field.
The value can be an integer between 1 and 1000.
:return: The ``OS/2.usWeightClass`` value.
:rtype: int
"""
return self.table.usWeightClass
@weight_class.setter
def weight_class(self, value: int) -> None:
"""
Sets the ``OS/2.usWeightClass`` value.
:param value: The value to set.
:type value: int
"""
if value < MIN_US_WEIGHT_CLASS or value > MAX_US_WEIGHT_CLASS:
raise ValueError(
f"Invalid value for usWeightClass: {value}. "
f"Expected a value between {MIN_US_WEIGHT_CLASS} and {MAX_US_WEIGHT_CLASS}."
)
self.table.usWeightClass = value
@property
def width_class(self) -> int:
"""
A property with getter and setter for the ``OS/2.usWidthClass`` field.
The value can be an integer between 1 and 9.
:return: The ``OS/2.usWidthClass`` value.
:rtype: int
"""
return self.table.usWidthClass
@width_class.setter
def width_class(self, value: int) -> None:
"""
Sets the ``OS/2.usWidthClass`` value.
:param value: The value to set.
:type value: int
"""
if value < MIN_US_WIDTH_CLASS or value > MAX_US_WIDTH_CLASS:
raise ValueError(
f"Invalid value for usWidthClass: {value}. "
f"Expected a value between {MIN_US_WIDTH_CLASS} and {MAX_US_WIDTH_CLASS}."
)
self.table.usWidthClass = value
@property
def embed_level(self) -> int:
"""
A property with getter and setter for the embedding level of the ``OS/2.fsType`` field.
The value can be 0, 2, 4 or 8.
* 0: Installable Embedding
* 2: Restricted License Embedding
* 4: Preview & Print Embedding
* 8: Editable Embedding
"""
return int(num2binary(self.table.fsType, 16)[9:17], 2)
@embed_level.setter
def embed_level(self, value: int) -> None:
"""
Sets the embedding level of the ``OS/2.fsType`` field.
:param value: The value to set.
:type value: int
"""
bit_operands = {
0: ([0, 1, 2, 3], None),
2: ([0, 2, 3], 1),
4: ([0, 1, 3], 2),
8: ([0, 1, 2], 3),
}
if value not in bit_operands:
raise ValueError("Invalid value: embed_level must be 0, 2, 4 or 8.")
bits_to_unset, bit_to_set = bit_operands[value]
for b in bits_to_unset:
self.set_bit(field_name="fsType", pos=b, value=False)
if bit_to_set is not None:
self.set_bit(field_name="fsType", pos=bit_to_set, value=True)
@property
def no_subsetting(self) -> bool:
"""
A property with getter and setter for bit 8 (NO_SUBSETTING) in the ``OS/2.fsType`` field.
:return: True if bit 8 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.table.fsType, NO_SUBSETTING_BIT)
@no_subsetting.setter
def no_subsetting(self, value: bool) -> None:
"""
Sets bit 8 (NO_SUBSETTING) of the ``OS/2.fsType`` field.
:param value: The value to set.
:type value: bool
"""
self.set_bit(field_name="fsType", pos=NO_SUBSETTING_BIT, value=value)
@property
def bitmap_embed_only(self) -> bool:
"""
A property with getter and setter for bit 9 (BITMAP_EMBED_ONLY) in the ``OS/2.fsType``
field.
:return: True if bit 9 is set, False otherwise.
:rtype: bool
"""
return is_nth_bit_set(self.table.fsType, BITMAP_EMBED_ONLY_BIT)
@bitmap_embed_only.setter
def bitmap_embed_only(self, value: bool) -> None:
"""
Sets bit 9 (BITMAP_EMBED_ONLY) of the ``OS/2.fsType`` field.
:param value: The value to set.
:type value: bool
"""
self.set_bit(field_name="fsType", pos=BITMAP_EMBED_ONLY_BIT, value=value)
@property
def vendor_id(self) -> str:
"""
A property with getter and setter for the ``OS/2.achVendID`` field.
When setting the property value, it is padded with spaces to a length of 4.
:return: The ``OS/2.achVendID`` value.
:rtype: str
"""
return self.table.achVendID
@vendor_id.setter
def vendor_id(self, value: str) -> None:
"""
Sets the ``OS/2.achVendID`` value.
:param value: The value to set.
:type value: str
"""
value = value.ljust(4, " ") if len(value) < 4 else value[:4]
self.table.achVendID = value
@property
def typo_ascender(self) -> int:
"""
A property with getter and setter for the ``OS/2.sTypoAscender`` field.
:return: The ``OS/2.sTypoAscender`` value.
:rtype: int
"""
return self.table.sTypoAscender
@typo_ascender.setter
def typo_ascender(self, value: int) -> None:
"""
Sets the ``OS/2.sTypoAscender`` value.
:param value: The value to set.
:type value: int
"""
self.table.sTypoAscender = value
@property
def typo_descender(self) -> int:
"""
A property with getter and setter for the ``OS/2.sTypoDescender`` field.
:return: The ``OS/2.sTypoDescender`` value.
:rtype: int
"""
return self.table.sTypoDescender
@typo_descender.setter
def typo_descender(self, value: int) -> None:
"""
Sets the ``OS/2.sTypoDescender`` value.
:param value: The value to set.
:type value: int
"""
self.table.sTypoDescender = value
@property
def typo_line_gap(self) -> int:
"""
A property with getter and setter for the ``OS/2.sTypoLineGap`` field.
:return: The ``OS/2.sTypoLineGap`` value.
:rtype: int
"""
return self.table.sTypoLineGap
@typo_line_gap.setter
def typo_line_gap(self, value: int) -> None:
"""
Sets the ``OS/2.sTypoLineGap`` value.
:param value: The value to set.
:type value: int
"""
self.table.sTypoLineGap = value
@property
def win_ascent(self) -> int:
"""
A property with getter and setter for the ``OS/2.usWinAscent`` field.
:return: The ``OS/2.usWinAscent`` value.
:rtype: int
"""
return self.table.usWinAscent
@win_ascent.setter
def win_ascent(self, value: int) -> None:
"""
Sets the ``OS/2.usWinAscent`` value.
:param value: The value to set.
:type value: int
"""
self.table.usWinAscent = value
@property
def win_descent(self) -> int:
"""
A property with getter and setter for the ``OS/2.usWinDescent`` field.
:return: The ``OS/2.usWinDescent`` value.
:rtype: int
"""
return self.table.usWinDescent
@win_descent.setter
def win_descent(self, value: int) -> None:
"""
Sets the ``OS/2.usWinDescent`` value.
:param value: The value to set.
:type value: int
"""
self.table.usWinDescent = value
@property
def x_height(self) -> int | None:
"""
A property with getter and setter for the ``OS/2.sxHeight`` field.
``sxHeight`` is only defined in ``OS/2`` table versions 2 and up.
:return: The ``OS/2.sxHeight`` value.
:rtype: int
"""
if self.version < 2:
return None
return self.table.sxHeight
@x_height.setter
def x_height(self, value: int) -> None:
"""
Sets the ``OS/2.sxHeight`` value.
:param value: The value to set.
:type value: int
:raises: InvalidOS2VersionError: If the ``OS/2`` table version is less than 2.
"""
if self.version < 2:
raise InvalidOS2VersionError(
"sxHeight is only defined in OS/2 table versions 2 and up."
)
self.table.sxHeight = value
@property
def cap_height(self) -> int | None:
"""
A property with getter and setter for the ``OS/2.sCapHeight`` field.
``sCapHeight`` is only defined in OS/2 table versions 2 and up.
:return: The ``OS/2.sCapHeight`` value.
:rtype: int
"""
if self.version < 2:
return None
return self.table.sCapHeight
@cap_height.setter
def cap_height(self, value: int) -> None:
"""
Sets the ``OS/2.sCapHeight`` value.
:param value: The value to set.
:type value: int
:raises: InvalidOS2VersionError: If the OS/2 table version is less than 2.
"""
if self.version < 2:
raise InvalidOS2VersionError(
"sCapHeight is only defined in OS/2 table versions 2 and up."
)
self.table.sCapHeight = value
@property
def max_context(self) -> int | None:
"""
A property with getter and setter for the ``OS/2.usMaxContext`` field.
``usMaxContext`` is only defined in OS/2 table versions 2 and up.
:return: The ``OS/2.usMaxContext`` value.
:rtype: int
"""
if self.version < 2:
return None
return self.table.usMaxContext
@max_context.setter
def max_context(self, value: int) -> None:
"""
Sets the ``OS/2.usMaxContext`` value.
:param value: The value to set.
:type value: int
:raises: InvalidOS2VersionError: If the OS/2 table version is less than 2.
"""
if self.version < 2:
raise InvalidOS2VersionError(
"usMaxContext is only defined in OS/2 table versions 2 and up."
)
self.table.usMaxContext = value
@property
def unicode_ranges(self) -> set[int]:
"""
A property with getter and setter for the ``OS/2.ulUnicodeRange(1-4)`` fields.
:return: The Unicode ranges of the ``OS/2`` table.
:rtype: set[int]
"""
return self.table.getUnicodeRanges()
@unicode_ranges.setter
def unicode_ranges(self, bits: set[int]) -> None:
"""
Sets the ``OS/2.ulUnicodeRange(1-4)`` fields.
:param bits: The Unicode ranges to set.
:type bits: set[int]
"""
self.table.setUnicodeRanges(bits)
@property
def codepage_ranges(self) -> set[int]:
"""
A property with getter and setter for the ``OS/2.ulCodePageRange(1-2)`` fields.
:return: The code page ranges of the ``OS/2`` table.
:rtype: set[int]
"""
return self.table.getCodePageRanges()
@codepage_ranges.setter
def codepage_ranges(self, bits: set[int]) -> None:
"""
Sets the ``OS/2.ulCodePageRange(1-2)`` fields.
:param bits: The code page ranges to set.
:type bits: set[int]
"""
self.table.setCodePageRanges(bits)
@property
def us_default_char(self) -> int | None:
"""
A property with getter and setter for the ``OS/2.usDefaultChar`` field.
:return: The ``OS/2.usDefaultChar`` value.
:rtype: int
"""
return getattr(self.table, "usDefaultChar", None)
@us_default_char.setter
def us_default_char(self, value: int) -> None:
"""
Sets the ``OS/2.usDefaultChar`` value.
:param value: The value to set.
:type value: int
:raises: InvalidOS2VersionError: If the OS/2 table version is less than 2.
"""
if self.version < 2:
raise InvalidOS2VersionError(
"usDefaultChar is only defined in OS/2 table versions 2 and up."
)
self.table.usDefaultChar = value
@property
def us_break_char(self) -> int | None:
"""
A property with getter and setter for the ``OS/2.usBreakChar`` field.
:return: The ``OS/2.usBreakChar`` value.
:rtype: int
"""
return getattr(self.table, "usBreakChar", None)
@us_break_char.setter
def us_break_char(self, value: int) -> None:
"""
Sets the ``OS/2.usBreakChar`` value.
:param value: The value to set.
:type value: int
:raises: InvalidOS2VersionError: If the OS/2 table version is less than 2.
"""
if self.version < 2:
raise InvalidOS2VersionError(
"usBreakChar is only defined in OS/2 table versions 2 and up."
)
self.table.usBreakChar = value
@property
def us_lower_optical_point_size(self) -> int | None:
"""
A property with getter and setter for the ``OS/2.usLowerOpticalPointSize`` field.
:return: The ``OS/2.usLowerOpticalPointSize`` value.
:rtype: int
"""
return getattr(self.table, "usLowerOpticalPointSize", None)
@us_lower_optical_point_size.setter
def us_lower_optical_point_size(self, value: int) -> None:
"""
Sets the ``OS/2.usLowerOpticalPointSize`` value.
:param value: The value to set.
:type value: int
:raises: InvalidOS2VersionError: If the OS/2 table version is less than 5.
"""
if self.version < 5:
raise InvalidOS2VersionError(
"usLowerOpticalPointSize is only defined in OS/2 table versions 5 and up."
)
self.table.usLowerOpticalPointSize = value
@property
def us_upper_optical_point_size(self) -> int | None:
"""
A property with getter and setter for the ``OS/2.usUpperOpticalPointSize`` field.
:return: The ``OS/2.usUpperOpticalPointSize`` value.
:rtype: int
"""
return getattr(self.table, "usUpperOpticalPointSize", None)
@us_upper_optical_point_size.setter
def us_upper_optical_point_size(self, value: int) -> None:
"""
Sets the ``OS/2.usUpperOpticalPointSize`` value.
:param value: The value to set.
:type value: int
:raises: InvalidOS2VersionError: If the OS/2 table version is less than 5.
"""
if self.version < 5:
raise InvalidOS2VersionError(
"usUpperOpticalPointSize is only defined in OS/2 table versions 5 and up."
)
self.table.usUpperOpticalPointSize = value
[docs]
def recalc_avg_char_width(self) -> int:
"""
Recalculates the ``OS/2.xAvgCharWidth`` value.
:return: The new ``OS/2.xAvgCharWidth`` value.
:rtype: int
"""
return self.table.recalcAvgCharWidth(ttFont=self.ttfont)
[docs]
def recalc_max_context(self) -> None:
"""Recalculates the ``OS/2.usMaxContext`` value."""
self.max_context = maxCtxFont(self.ttfont)
[docs]
def recalc_unicode_ranges(self, percentage: float = 33.0) -> set[tuple[int, str, str]]:
"""
Recalculates the ``OS/2.ulUnicodeRange(1-4)`` fields.
:param percentage: The percentage of codepoints that must be present in a Unicode block for
it to be enabled. Default is 33.0.
:type percentage: float
:return: A set of tuples with the modified Unicode ranges.
:rtype: set[tuple[int, str, str]]
:raises: KeyError: If the font does not have a cmap table.
"""
if T_CMAP not in self.ttfont:
raise KeyError("Font does not have a cmap table")
cmap_table = self.ttfont[T_CMAP]
unicodes = set()
has_cmap_32 = False
for table in cmap_table.tables:
if table.isUnicode():
unicodes.update(table.cmap.keys())
if table.platformID == 3 and table.platEncID == 10:
has_cmap_32 = True
new_unicode_ranges = set()
modified_unicode_ranges = set()
for block in OS_2_UNICODE_RANGES:
min_codepoints = otRound(block.size / 100 * percentage) if block.bit_number != 57 else 1
found_codepoints = count_block_codepoints(block, unicodes)
is_enabled = block.bit_number in self.unicode_ranges
is_supported = check_block_support(
block, found_codepoints, min_codepoints, self.version, has_cmap_32
)
if is_supported:
new_unicode_ranges.add(block.bit_number)
if not is_enabled:
modified_unicode_ranges.add((block.bit_number, block.block_name, "enabled"))
else:
if is_enabled:
modified_unicode_ranges.add((block.bit_number, block.block_name, "disabled"))
self.unicode_ranges = new_unicode_ranges
return modified_unicode_ranges
[docs]
def recalc_code_page_ranges(self) -> None:
"""Recalculates the code page ranges of the ``OS/2`` table."""
self.table.recalcCodePageRanges(self.ttfont)
[docs]
def upgrade(self, target_version: int) -> None:
"""
Upgrades the ``OS/2`` table to a more recent version.
:param target_version: The target version to upgrade to.
:type target_version: int
:raises: InvalidOS2VersionError: If the target version is less than the current version.
"""
current_version = self.version
if target_version <= current_version:
raise InvalidOS2VersionError(
f"The target version must be greater than the current version ({current_version})."
)
# Used to recalc xHeight and capHeight
def _get_glyph_height(glyph_name: str) -> int:
glyph_set = self.ttfont.getGlyphSet()
if glyph_name not in glyph_set:
return 0
bounds_pen = BoundsPen(glyphSet=glyph_set)
glyph_set[glyph_name].draw(bounds_pen)
return otRound(bounds_pen.bounds[3])
self.version = target_version
if current_version < 1:
self.recalc_code_page_ranges()
if target_version == 1:
return
if current_version < 2:
self.x_height = _get_glyph_height("x")
self.cap_height = _get_glyph_height("H")
self.us_default_char = 0
self.us_break_char = 32
self.recalc_max_context()
if target_version == 5:
self.us_lower_optical_point_size = 0
self.us_upper_optical_point_size = int(65535 / 20)
if target_version < 4:
self.fs_selection.use_typo_metrics = False
self.fs_selection.wws_consistent = False
self.fs_selection.oblique = False