Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update iOS importer to use new metadata #639

Merged
merged 5 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions importer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
"finalize:android": "replace '#212121' '@color/fluent_default_icon_tint' ./dist --exclude=\"*.selector\" --recursive --quiet && replace '\"http://schemas.android.com/apk/res/android\"' '\"http://schemas.android.com/apk/res/android\" android:autoMirrored=\"true\"' $(awk '$0=\"./dist/\"$0\".xml\"' rtl.txt)",
"create:android": "npm run generate:svg-android && find ./dist/ -type d -exec sh -c 'tools/vd-tool/bin/vd-tool -c -in {} -out {}' \\;",
"optimize:android": "find ./dist/ -type f -name '*.svg' -delete && find ./dist/ -type d ! -name '*_selector.xml' -exec sh -c 'avocado -q {}' \\;",
"build:ios": "npm run generate:svg",
"build:react": "npm run generate:react",
"deploy:android": "npm run build:android && rm -rf ../android/library/src/main/res/drawable && mkdir ../android/library/src/main/res/drawable && find ./dist/ -type f -name \"*.xml\" -maxdepth 1 -exec cp {} ../android/library/src/main/res/drawable \\; && npm run clean",
"deploy:react": "npm run build:react && rm -rf ../react/src/components && mkdir ../react/src/components && find ./dist/ -type f -name \"*.tsx\" -exec cp {} ../react/src/components \\; && npm run clean",
"deploy:ios": "npm run build:ios && python3 process_ios_assets.py && npm run clean",
"deploy:ios": "python3 process_ios_assets.py",
"generate:font-regular": "node generateFont.js --source=dist --dest=dist/fonts --iconType=Regular --codepoints=../fonts/FluentSystemIcons-Regular.json",
"generate:font-filled": "node generateFont.js --source=dist --dest=dist/fonts --iconType=Filled --codepoints=../fonts/FluentSystemIcons-Filled.json",
"generate:font-resizable": "node generateFont.js --source=dist --dest=dist/fonts --iconType=Resizable",
Expand Down
257 changes: 135 additions & 122 deletions importer/process_ios_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import os
import shutil
from collections import defaultdict
from collections import namedtuple

FluentIconAsset = namedtuple('FluentIconAsset', ['name', 'path', 'metadata'])

LIBRARY_NAME = 'FluentIcon'

Expand All @@ -17,10 +20,10 @@ def to_camel_case(snake_str):
RESERVED_WORDS = ['repeat', 'import', 'class']

ICON_PREFIX = "ic_fluent_"
IMAGE_FORMAT = ".svg"
IMAGE_FORMAT = "svg"

def get_icon_name(file_name):
return file_name.replace(IMAGE_FORMAT, '').replace('__', '_').replace('_ltr_', '_').replace('_rtl_', '_')
return file_name.replace(f".{IMAGE_FORMAT}", '').replace('__', '_').replace('_ltr_', '_').replace('_rtl_', '_').replace('_ltr', '').replace('_rtl', '')


def bucket_array(array, bucket_size):
Expand All @@ -39,136 +42,148 @@ def bucket_array(array, bucket_size):
return output


def xc_image_data_for_file_name(file_name, locale):
def xc_image_data_for_file_name(file_name, locale, metadata):
output = {
"idiom": "universal",
"filename": file_name
}
if locale is not None:
output["locale"] = locale

if "_ltr_" in file_name:
if metadata.get("directionType") == "mirror":
# iOS will mirror in this configuration from ltr
output["language-direction"] = "left-to-right"
elif metadata.get("directionType") == "unique":
if metadata["singleton"] == "LTR":
output["language-direction"] = "left-to-right"
elif metadata["singleton"] == "RTL":
output["language-direction"] = "right-to-left"

# These icons are being replaced by new icons with metadata
# for backward compatibility we add the language-direction
if "_ltr_" in file_name and not metadata:
output["language-direction"] = "left-to-right"
elif "_rtl_" in file_name:
if "_rtl_" in file_name and not metadata:
output["language-direction"] = "right-to-left"

return output

# from an array of lang/loc directory names, insert the localization information for a particular icon
# Copies an image to the directory for that asset and then inserts the locale information in Contents.json
def add_localized_set(lang_locs, original_icon_names, icon_assets_path):
for lang_loc in lang_locs:
file_names = os.listdir(os.path.join("dist", lang_loc))
for file_name in file_names:

def create_icon_set(fluent_icon_assets, original_icon_names, icon_assets_path):
for fluent_icon_asset in fluent_icon_assets:
language_metadata = fluent_icon_asset.metadata.get("language")
languages = []
if language_metadata:
for language_group in language_metadata:
for language in language_group["locale"]:
languages.append(language)

# If we only have one localized icon, disable localization support
if languages == ["en"]:
languages = []

for file_name in os.listdir(os.path.join(fluent_icon_asset.path, IMAGE_FORMAT.upper())):
icon_name = get_icon_name(file_name)
imageset_path = os.path.join(icon_assets_path, "{icon_name}.imageset".format(icon_name=icon_name))
if not os.path.exists(imageset_path):
print(f"WARNING: No base localized icon {icon_name}")
os.mkdir(imageset_path)

# Xcode is specific about the capitalization used for locales.
# "bg" -> "bg"
# "bg-bg" -> "bg-BG"
# "sr-latn" -> "sr-Latn"
# If it is too specific it won't be supported by iOS or Mac so we can ignore it.
# "sr-latn-rs"
locale_components = lang_loc.split("-")
if len(locale_components) == 3:
print(f"DEBUG: Unused localization {icon_name} {lang_loc}")

if icon_name in original_icon_names:
continue
elif len(locale_components) == 2:
if len(locale_components[1]) == 2:
asset_locale = locale_components[0] + "-" + locale_components[1].upper()

original_icon_names.add(icon_name)

imageset_path = os.path.join(icon_assets_path, "{icon_name}.imageset".format(icon_name=icon_name))
os.mkdir(imageset_path)
shutil.copyfile(os.path.join(fluent_icon_asset.path, IMAGE_FORMAT.upper(), file_name), os.path.join(imageset_path, file_name))

supported_languages = []
for language in languages:
if language in supported_languages:
# Ignore duplicates
continue

# Xcode is specific about the capitalization used for locales.
# "bg" -> "bg"
# "bg-bg" -> "bg-BG"
# "sr-latn" -> "sr-Latn"
# If it is too specific it won't be supported by iOS or Mac so we can ignore it.
# "sr-latn-rs"
locale_components = language.split("-")
if len(locale_components) == 3:
print(f"DEBUG: Unused localization {icon_name} {language}")
continue
elif len(locale_components) == 2:
if len(locale_components[1]) == 2:
asset_locale = locale_components[0] + "-" + locale_components[1].upper()
else:
asset_locale = locale_components[0] + "-" + locale_components[1].title()
else:
asset_locale = locale_components[0] + "-" + locale_components[1].title()
else:
asset_locale = lang_loc
asset_locale = language

localized_icon_path = os.path.join(fluent_icon_asset.path, language, IMAGE_FORMAT.upper(), file_name)
if os.path.exists(localized_icon_path):
shutil.copyfile(localized_icon_path, os.path.join(imageset_path, language + "_" + file_name))
supported_languages.append(language)

shutil.copyfile(os.path.join("dist", lang_loc, file_name), os.path.join(imageset_path, lang_loc + "_" + file_name))
imageset_contents_path = os.path.join(imageset_path, "Contents.json")
contents_json = json.load(open(imageset_contents_path))

loc_image_data = []
if lang_loc == "zh":
# For the Chinese locale, explicitly differentiate between Simplified Chinese (zh_CN) and Traditional Chinese (zh_TW)
# While Apple's Automatic Localization in its Asset Catalogs support a superset Chinese locale id `zh`, we're adding two -
# even more explicit - locale ids pertaining to this language. But this doesn't mean that we need two separate .svg assets.
# Instead add the same zh_ic_fluent_ file under the two available Chinese Locale sets by adjusting the Contents.json metadata file.
loc_image_data.append(xc_image_data_for_file_name(lang_loc + "_" + file_name, locale="zh_CN"))
loc_image_data.append(xc_image_data_for_file_name(lang_loc + "_" + file_name, locale="zh_TW"))
else:
loc_image_data.append(xc_image_data_for_file_name(lang_loc + "_" + file_name, locale=lang_loc))

contents_json["properties"]["localizable"] = True
contents_json["images"].extend(loc_image_data)

with open(imageset_contents_path, 'w') as imageset:
imageset.write(json.dumps(contents_json, indent=2, sort_keys=True))

def create_icon_set(file_names, original_icon_names, icon_assets_path):
for file_name in file_names:
icon_name = get_icon_name(file_name)
rendering_intent = "template"
if icon_name.endswith("_color") or icon_name.startswith("ic_fluent_flag_pride"):
rendering_intent = "original"

images = [
xc_image_data_for_file_name(file_name, locale=None, metadata=fluent_icon_asset.metadata)
]

contents = {
"images": images,
"info": {
"version": 1,
"author": "xcode"
},
"properties": {
"template-rendering-intent": rendering_intent
}
}

if icon_name in original_icon_names:
continue
if len(supported_languages):
contents["properties"]["localizable"] = True

original_icon_names.add(icon_name)
for language in sorted(supported_languages):
loc_image_data = []
if language == "zh":
# For the Chinese locale, explicitly differentiate between Simplified Chinese (zh_CN) and Traditional Chinese (zh_TW)
# While Apple's Automatic Localization in its Asset Catalogs support a superset Chinese locale id `zh`, we're adding two -
# even more explicit - locale ids pertaining to this language. But this doesn't mean that we need two separate .svg assets.
# Instead add the same zh_ic_fluent_ file under the two available Chinese Locale sets by adjusting the Contents.json metadata file.
loc_image_data.append(xc_image_data_for_file_name(language + "_" + file_name, locale="zh_CN", metadata=fluent_icon_asset.metadata))
loc_image_data.append(xc_image_data_for_file_name(language + "_" + file_name, locale="zh_TW", metadata=fluent_icon_asset.metadata))
else:
loc_image_data.append(xc_image_data_for_file_name(language + "_" + file_name, locale=language, metadata=fluent_icon_asset.metadata))
contents["images"].extend(loc_image_data)

sibling_file_name = None
if "_ltr_" in file_name:
sibling_file_name = file_name.replace("_ltr_", "_rtl_")
elif "_rtl_" in file_name:
sibling_file_name = file_name.replace("_rtl_", "_ltr_")

if sibling_file_name is not None:
if not os.path.exists(os.path.join("dist", sibling_file_name)):
print(f"WARNING: No corresponding localized icon {sibling_file_name}")
sibling_file_name = None

imageset_path = os.path.join(icon_assets_path, "{icon_name}.imageset".format(icon_name=icon_name))
os.mkdir(imageset_path)
shutil.copyfile(os.path.join("dist", file_name), os.path.join(imageset_path, file_name))
if sibling_file_name is not None:
shutil.copyfile(os.path.join("dist", sibling_file_name), os.path.join(imageset_path, sibling_file_name))

imageset_contents_path = os.path.join(imageset_path, "Contents.json")

with open(imageset_contents_path, 'w') as imageset:
rendering_intent = "template"
if icon_name.endswith("_color") or icon_name.startswith("ic_fluent_flag_pride"):
rendering_intent = "original"

images = [
xc_image_data_for_file_name(file_name, locale=None)
]
if sibling_file_name is not None:
images.append(xc_image_data_for_file_name(sibling_file_name, locale=None))

contents = {
"images": images,
"info": {
"version": 1,
"author": "xcode"
},
"properties": {
"template-rendering-intent": rendering_intent
}
}
imageset.write(json.dumps(contents, indent=2, sort_keys=True))

imageset.write(json.dumps(contents, indent=2, sort_keys=True))

def process_assets():
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
assets_dir = os.path.join(project_root, "assets")

file_names = []
loc_names = []
for file_name in os.listdir("dist"):
if file_name.endswith(IMAGE_FORMAT):
file_names.append(file_name)
elif os.path.isdir(os.path.join("dist", file_name)):
loc_names.append(file_name)

file_names.sort()
icon_assets = []
for file_name in os.listdir(assets_dir):
if file_name == ".DS_Store":
continue
icon_assets_path = os.path.join(assets_dir, file_name)
metadata_path = os.path.join(icon_assets_path, "metadata.json")
metadata = {}
if os.path.exists(metadata_path):
with open(metadata_path) as metadata_json:
metadata = json.loads(metadata_json.read())

icon_assets.append(FluentIconAsset(
name=file_name,
path=icon_assets_path,
metadata=metadata
))

ios_directory = os.path.join(project_root, "ios")

Expand All @@ -178,14 +193,15 @@ def process_assets():
os.mkdir(icon_assets_path)

original_icon_names = set()
create_icon_set(file_names, original_icon_names, icon_assets_path)
add_localized_set(loc_names, original_icon_names, icon_assets_path)
create_icon_set(icon_assets, original_icon_names, icon_assets_path)

# Generate BUILD.gn for GN build system
gn_path = os.path.join(ios_directory, "BUILD.gn")
if os.path.exists(gn_path):
os.remove(gn_path)

imagesets = sorted(os.listdir(icon_assets_path))

with open(gn_path, 'w+') as gn_file:
gn_file.write("#\n")
gn_file.write("# Copyright (c) Microsoft Corporation. All rights reserved.\n")
Expand All @@ -197,23 +213,17 @@ def process_assets():

gn_file.write("import(\"//build/config/ios/asset_catalog.gni\")\n\n")

imageset_names = set()
for file_name in file_names:
imageset_name = get_icon_name(file_name)
# GN targets do not allow duplicate names
if imageset_name in imageset_names:
for imageset in imagesets:
if imageset == ".DS_Store":
continue
imageset_names.add(imageset_name)
imageset_folder_path = ios_directory + '/FluentIcons/Assets/IconAssets.xcassets/' + imageset_name + '.imageset'

gn_file.write("imageset(\"{}\")".format(imageset_name) + " {\n")
imageset_folder_path = os.path.join(icon_assets_path, imageset)
gn_file.write("imageset(\"{}\")".format(imageset.replace(".imageset", "")) + " {\n")
gn_file.write(" sources = [\n")
gn_file.write(" \"FluentIcons/Assets/IconAssets.xcassets/{}.imageset/Contents.json\"".format(imageset_name) + ",\n")
for imageset_file in os.listdir(imageset_folder_path):
if os.path.splitext(imageset_file)[1] == IMAGE_FORMAT:
gn_file.write(" \"FluentIcons/Assets/IconAssets.xcassets/{imageset_name}.imageset/{icon_file_name}\"".format(imageset_name=imageset_name, icon_file_name=imageset_file) + ",\n")
for imageset_file in sorted(os.listdir(imageset_folder_path)):
gn_file.write(f" \"FluentIcons/Assets/IconAssets.xcassets/{imageset}/{imageset_file}\",\n")
gn_file.write(" ]\n")
gn_file.write("}\n\n")

swift_enum_path = os.path.join(ios_directory, LIBRARY_NAME + "s", "Classes", LIBRARY_NAME + ".swift")
if os.path.exists(swift_enum_path):
os.remove(swift_enum_path)
Expand All @@ -222,14 +232,17 @@ def process_assets():
all_sizes = set()
original_icon_names = set()

for file_name in file_names:
for imageset in imagesets:
"""
Remove first and last two components

Before: ic_fluent_flash_off_24_regular.svg
After: flash_off_24
"""
icon_name = get_icon_name(file_name).replace(ICON_PREFIX, '')
if imageset == ".DS_Store":
continue

icon_name = get_icon_name(imageset.replace(".imageset", "")).replace(ICON_PREFIX, "")

if icon_name in original_icon_names:
continue
Expand Down
Loading