<commit_before># Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from collections import OrderedDict, defaultdict
from functools import partial
import logging
import os
import re
from textwrap import dedent
from typing import Dict

from fontTools import designspaceLib

from glyphsLib import classes, util
from .constants import PUBLIC_PREFIX, FONT_CUSTOM_PARAM_PREFIX, GLYPHLIB_PREFIX
from .axes import WEIGHT_AXIS_DEF, WIDTH_AXIS_DEF, find_base_style, class_to_value

GLYPH_ORDER_KEY = PUBLIC_PREFIX + "glyphOrder"

BRACKET_LAYER_RE = re.compile(r".*(?P<first_bracket>[\[\]])\s*(?P<value>\d+)\s*\].*")
BRACKET_GLYPH_TEMPLATE = "{glyph_name}.{rev}BRACKET.{location}"
REVERSE_BRACKET_LABEL = "REV_"
BRACKET_GLYPH_RE = re.compile(
    r"(?P<glyph_name>.+)\.(?P<rev>{})?BRACKET\.(?P<location>\d+)$".format(
        REVERSE_BRACKET_LABEL
    )
)


class _LoggerMixin:

    _logger = None

    @property
    def logger(self):
        if self._logger is None:
            self._logger = logging.getLogger(
                ".".join([self.__class__.__module__, self.__class__.__name__])
            )
        return self._logger


class UFOBuilder(_LoggerMixin):
    """Builder for Glyphs to UFO + designspace."""

    def __init__(
        self,
        font,
        ufo_module=None,
        designspace_module=designspaceLib,
        family_name=None,
        instance_dir=None,
        propagate_anchors=True,
        use_designspace=False,
        minimize_glyphs_diffs=False,
        generate_GDEF=True,
        store_editor_state=True,
        write_skipexportglyphs=False,
    ):
        """Create a builder that goes from Glyphs to UFO + designspace.

        Keyword arguments:
        font -- The GSFont object to transform into UFOs
        ufo_module -- A Python module to use to build UFO objects (you can pass
                      a custom module that has the same classes as ufoLib2 or
                      defcon to get instances of your own classes). Default: ufoLib2
        designspace_module -- A Python module to use to build a Designspace
                              Document. Default is fontTools.designspaceLib.
        family_name -- if provided, the master UFOs will be given this name and
                       only instances with this name will be returned.
        instance_dir -- if provided, instance UFOs will be located in this
                        directory, according to their Designspace filenames.
        propagate_anchors -- set to False to prevent anchor propagation
        use_designspace -- set to True to make optimal use of the designspace:
                           data that is common to all ufos will go there.
        minimize_glyphs_diffs -- set to True to store extra info in UFOs
                                 in order to get smaller diffs between .glyphs
                                 .glyphs files when going glyphs->ufo->glyphs.
        generate_GDEF -- set to False to skip writing a `table GDEF {...}` in
                         the UFO features.
        store_editor_state -- If True, store editor state in the UFO like which
                              glyphs are open in which tabs ("DisplayStrings").
        write_skipexportglyphs -- If True, write the export status of a glyph
                                         into the UFOs' and Designspace's lib instead
                                         of the glyph level lib key
                                         "com.schriftgestaltung.Glyphs.Export".
        """
        self.font = font

        if ufo_module is None:
            import ufoLib2 as ufo_module

        self.ufo_module = ufo_module

        self.designspace_module = designspace_module
        self.instance_dir = instance_dir
        self.propagate_anchors = propagate_anchors
        self.use_designspace = use_designspace
        self.minimize_glyphs_diffs = minimize_glyphs_diffs
        self.generate_GDEF = generate_GDEF
        self.store_editor_state = store_editor_state
        self.bracket_layers = []
        self.write_skipexportglyphs = write_skipexportglyphs

        # The set of (SourceDescriptor + UFO)s that will be built,
        # indexed by master ID, the same order as masters in the source GSFont.
        self._sources = OrderedDict()

        # A cache for mappings of layer IDs to mappings of glyph names to Glyphs layers,
        # for passing into pens as glyph sets.
        self._glyph_sets: Dict[str, Dict[str, classes.GSLayer]] = {}

        # The designSpaceDocument object that will be built.
        # The sources will be built in any case, at the same time that we build
        # the master UFOs, when the user requests them.
        # The axes, instances, rules... will only be built if the designspace
        # document itself is requested by the user.
        self._designspace = self.designspace_module.DesignSpaceDocument()
        self._designspace_is_complete = False

        # If any glyph layer has the properties "vertWidth" or "vertOrigin"
        # set, Glyphsapp will assume the font is going to be used for
        # vertical typesetting. When Glyphsapp generates these fonts, it
        # will include the vhea and vmtx tables.
        self.is_vertical = self._is_vertical()

        # check that source was generated with at least stable version 2.3
        # https://github.com/googlefonts/glyphsLib/pull/65#issuecomment-237158140
        if int(font.appVersion) < 895:
            self.logger.warning(
                "This Glyphs source was generated with an outdated version "
                "of Glyphs. The resulting UFOs may be incorrect."
            )

        # check that source does not include the old custom
        # parameter name "Variation Font Origin".  As of sometime prior to
        # Glyphs v2.6.4 (1286) build, the custom parameter name was
        # changed to "Variable Font Origin" from "Variation Font Origin"
        if "Variation Font Origin" in font.customParameters:
            self.logger.warning(
                "This Glyphs source was generated with an outdated version "
                "of Glyphs. Please update the 'Variation Font Origin' "
                "custom parameter to 'Variable Font Origin'. The resulting "
                "UFOs may be incorrect."
            )

        if family_name is None:
            # use the source family name, and include all the instances
            self.family_name = self.font.familyName
            self._do_filter_instances_by_family = False
        else:
            self.family_name = family_name
            # use a custom 'family_name' to name master UFOs, and only build
            # instances with matching 'familyName' custom parameter
            self._do_filter_instances_by_family = True

    def _is_vertical(self):
        for glyph in self.font.glyphs:
            for layer in glyph.layers:
                if layer.vertWidth is not None or layer.vertOrigin is not None:
                    return True
        return False

    @property
    def masters(self):
        """Get an iterator over master UFOs that match the given family_name.
        """
        if self._sources:
            for source in self._sources.values():
                yield source.font
            return

        # Store set of actually existing master (layer) ids. This helps with
        # catching dangling layer data that Glyphs may ignore, e.g. when
        # copying glyphs from other fonts with, naturally, different master
        # ids. Note: Masters have unique ids according to the Glyphs
        # documentation and can therefore be stored in a set.
        master_layer_ids = {m.id for m in self.font.masters}

        # stores background data from "associated layers"
        supplementary_layer_data = []

        # TODO(jamesgk) maybe create one font at a time to reduce memory usage<commit_msg>Only set is_vertical to True if layers are associated with masters<commit_after># Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from collections import OrderedDict, defaultdict
from functools import partial
import logging
import os
import re
from textwrap import dedent
from typing import Dict

from fontTools import designspaceLib

from glyphsLib import classes, util
from .constants import PUBLIC_PREFIX, FONT_CUSTOM_PARAM_PREFIX, GLYPHLIB_PREFIX
from .axes import WEIGHT_AXIS_DEF, WIDTH_AXIS_DEF, find_base_style, class_to_value

GLYPH_ORDER_KEY = PUBLIC_PREFIX + "glyphOrder"

BRACKET_LAYER_RE = re.compile(r".*(?P<first_bracket>[\[\]])\s*(?P<value>\d+)\s*\].*")
BRACKET_GLYPH_TEMPLATE = "{glyph_name}.{rev}BRACKET.{location}"
REVERSE_BRACKET_LABEL = "REV_"
BRACKET_GLYPH_RE = re.compile(
    r"(?P<glyph_name>.+)\.(?P<rev>{})?BRACKET\.(?P<location>\d+)$".format(
        REVERSE_BRACKET_LABEL
    )
)


class _LoggerMixin:

    _logger = None

    @property
    def logger(self):
        if self._logger is None:
            self._logger = logging.getLogger(
                ".".join([self.__class__.__module__, self.__class__.__name__])
            )
        return self._logger


class UFOBuilder(_LoggerMixin):
    """Builder for Glyphs to UFO + designspace."""

    def __init__(
        self,
        font,
        ufo_module=None,
        designspace_module=designspaceLib,
        family_name=None,
        instance_dir=None,
        propagate_anchors=True,
        use_designspace=False,
        minimize_glyphs_diffs=False,
        generate_GDEF=True,
        store_editor_state=True,
        write_skipexportglyphs=False,
    ):
        """Create a builder that goes from Glyphs to UFO + designspace.

        Keyword arguments:
        font -- The GSFont object to transform into UFOs
        ufo_module -- A Python module to use to build UFO objects (you can pass
                      a custom module that has the same classes as ufoLib2 or
                      defcon to get instances of your own classes). Default: ufoLib2
        designspace_module -- A Python module to use to build a Designspace
                              Document. Default is fontTools.designspaceLib.
        family_name -- if provided, the master UFOs will be given this name and
                       only instances with this name will be returned.
        instance_dir -- if provided, instance UFOs will be located in this
                        directory, according to their Designspace filenames.
        propagate_anchors -- set to False to prevent anchor propagation
        use_designspace -- set to True to make optimal use of the designspace:
                           data that is common to all ufos will go there.
        minimize_glyphs_diffs -- set to True to store extra info in UFOs
                                 in order to get smaller diffs between .glyphs
                                 .glyphs files when going glyphs->ufo->glyphs.
        generate_GDEF -- set to False to skip writing a `table GDEF {...}` in
                         the UFO features.
        store_editor_state -- If True, store editor state in the UFO like which
                              glyphs are open in which tabs ("DisplayStrings").
        write_skipexportglyphs -- If True, write the export status of a glyph
                                         into the UFOs' and Designspace's lib instead
                                         of the glyph level lib key
                                         "com.schriftgestaltung.Glyphs.Export".
        """
        self.font = font

        if ufo_module is None:
            import ufoLib2 as ufo_module

        self.ufo_module = ufo_module

        self.designspace_module = designspace_module
        self.instance_dir = instance_dir
        self.propagate_anchors = propagate_anchors
        self.use_designspace = use_designspace
        self.minimize_glyphs_diffs = minimize_glyphs_diffs
        self.generate_GDEF = generate_GDEF
        self.store_editor_state = store_editor_state
        self.bracket_layers = []
        self.write_skipexportglyphs = write_skipexportglyphs

        # The set of (SourceDescriptor + UFO)s that will be built,
        # indexed by master ID, the same order as masters in the source GSFont.
        self._sources = OrderedDict()

        # A cache for mappings of layer IDs to mappings of glyph names to Glyphs layers,
        # for passing into pens as glyph sets.
        self._glyph_sets: Dict[str, Dict[str, classes.GSLayer]] = {}

        # The designSpaceDocument object that will be built.
        # The sources will be built in any case, at the same time that we build
        # the master UFOs, when the user requests them.
        # The axes, instances, rules... will only be built if the designspace
        # document itself is requested by the user.
        self._designspace = self.designspace_module.DesignSpaceDocument()
        self._designspace_is_complete = False

        # If any glyph layer which is associated with a GSFontMaster has the
        # properties "vertWidth" or "vertOrigin" set, Glyphsapp will assume
        # the font is going to be used for vertical typesetting. When
        # Glyphsapp generates these fonts, it will include the vhea, vmtx,
        # VORG tables. VORG will only be included if the font is an otf.
        self.is_vertical = self._is_vertical()

        # check that source was generated with at least stable version 2.3
        # https://github.com/googlefonts/glyphsLib/pull/65#issuecomment-237158140
        if int(font.appVersion) < 895:
            self.logger.warning(
                "This Glyphs source was generated with an outdated version "
                "of Glyphs. The resulting UFOs may be incorrect."
            )

        # check that source does not include the old custom
        # parameter name "Variation Font Origin".  As of sometime prior to
        # Glyphs v2.6.4 (1286) build, the custom parameter name was
        # changed to "Variable Font Origin" from "Variation Font Origin"
        if "Variation Font Origin" in font.customParameters:
            self.logger.warning(
                "This Glyphs source was generated with an outdated version "
                "of Glyphs. Please update the 'Variation Font Origin' "
                "custom parameter to 'Variable Font Origin'. The resulting "
                "UFOs may be incorrect."
            )

        if family_name is None:
            # use the source family name, and include all the instances
            self.family_name = self.font.familyName
            self._do_filter_instances_by_family = False
        else:
            self.family_name = family_name
            # use a custom 'family_name' to name master UFOs, and only build
            # instances with matching 'familyName' custom parameter
            self._do_filter_instances_by_family = True

    def _is_vertical(self):
        master_ids = set(m.id for m in self.font.masters)
        for glyph in self.font.glyphs:
            for layer in glyph.layers:
                if layer.layerId not in master_ids:
                    continue
                if layer.vertWidth is not None or layer.vertOrigin is not None:
                    return True
        return False

    @property
    def masters(self):
        """Get an iterator over master UFOs that match the given family_name.
        """
        if self._sources:
            for source in self._sources.values():
                yield source.font
            return

        # Store set of actually existing master (layer) ids. This helps with
        # catching dangling layer data that Glyphs may ignore, e.g. when
        # copying glyphs from other fonts with, naturally, different master
        # ids. Note: Masters have unique ids according to the Glyphs
        # documentation and can therefore be stored in a set.
        master_layer_ids = {m.id for m in self.font.masters}

        # stores background data from "associated layers"
        supplementary_layer_data = []

        # TODO(jamesgk) maybe create one font at a time to reduce memory usage
{'fraction_non_alphanumeric': 0.06147181078391195, 'fraction_numerical': 0.003981031555529536, 'mean_word_length': 2.2211955496888556, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 17, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6591770', 'n_tokens_mistral': 4649, 'n_tokens_neox': 4396, 'n_words': 1702}