flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
166.19k stars 27.49k forks source link

Support woff2 font #109108

Open nawaf11 opened 2 years ago

nawaf11 commented 2 years ago

Use case

Welcome I have an app that use a lot of fonts files, currently I am using .woff format. It works on:

It does not work on:

Proposal

I hope that you add support for .woff2 format in Android & IOS.

It will significantly decrease my app size.

exaby73 commented 2 years ago

Hello @nawaf11. Leaving this issue open for insights from the team. Thank you

guidezpl commented 1 year ago

Related to https://github.com/flutter/flutter/issues/63163, the documentation does not say which platforms are supported or not.

Which platforms currently support .woff2 fonts?

Update: minimal testing seems to show lack of support for web, and support macOS and iOS.

image

Android, Windows, and Linux also don't seem to support .woff2.

dnfield commented 1 year ago

Doing this is simple with freetype2 but requires adding brotli decoding to the engine. Local tests show this adds about 157kb uncompressed to a 32 bit build of Android's libflutter.so (about a 2% increase). Arm 32 bit flutter.jar in release with LTO grows by about 81kb (1.75%).

dnfield commented 1 year ago

Most of that change is likely brotli's decoding dictionary, which is about 120kb uncompressed.

dnfield commented 1 year ago

We don't really have a great reason to do this.

woff2 would force us to include a brotli decoder we don't otherwise need. It's a bigger binary hit than just using properly sized and compressed TTF files in most cases. There is public API available in woff to convert to ttf.

dnfield commented 1 year ago

For future me:

diff in third_party/freetype2:

diff --git a/BUILD.gn b/BUILD.gn
index 86217752f..2152344f3 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -55,9 +55,9 @@ target(default_library_type, "freetype2") {
     "src/base/ftinit.c",
     "src/base/ftmm.c",
     "src/base/ftstroke.c",
+    "src/base/ftsynth.c",
     "src/base/ftsystem.c",
     "src/base/fttype1.c",
-    "src/base/ftsynth.c",
     "src/gzip/ftgzip.c",
     "src/lzw/ftlzw.c",
     "src/psaux/psaux.c",
@@ -67,21 +67,26 @@ target(default_library_type, "freetype2") {
     "src/smooth/smooth.c",

     # Font Drivers. Drivers need to be enabled in ftmodule.h explicitly.
-    "src/cff/cff.c",           # OpenType, (.cff, .cef)
-    "src/truetype/truetype.c", # TrueType
+    "src/cff/cff.c",  # OpenType, (.cff, .cef)
+    "src/truetype/truetype.c",  # TrueType
   ]

   defines = [
     "FT2_BUILD_LIBRARY",
     "DARWIN_NO_CARBON",
+
     # Long directory name to avoid accidentally using wrong headers.
     "FT_CONFIG_MODULES_H=<freetype-flutter-config/ftmodule.h>",
     "FT_CONFIG_OPTIONS_H=<freetype-flutter-config/ftoption.h>",
   ]

-  public_configs = [ ":freetype_config" ]
+  public_configs = [
+    ":freetype_config",
+    "//third_party/brotli:brotli_config",
+  ]

   deps = [
+    "//third_party/brotli:brotlidec",
     "//third_party/libpng",
     "//third_party/zlib",
   ]
diff --git a/include/freetype-flutter-config/ftoption.h b/include/freetype-flutter-config/ftoption.h
index fa587e477..c0c317ccd 100644
--- a/include/freetype-flutter-config/ftoption.h
+++ b/include/freetype-flutter-config/ftoption.h
@@ -240,6 +240,20 @@ FT_BEGIN_HEADER
   /*                                                                       */
 /* #define FT_CONFIG_OPTION_USE_HARFBUZZ */

+/**************************************************************************
+ *
+ * Brotli support.
+ *
+ *   FreeType uses the Brotli library to provide support for decompressing
+ *   WOFF2 streams.
+ *
+ *   Define this macro if you want to enable this 'feature'.
+ *
+ *   If you use a build system like cmake or the `configure` script,
+ *   options set by those programs have precedence, overwriting the value
+ *   here with the configured one.
+ */
+#define FT_CONFIG_OPTION_USE_BROTLI

   /*************************************************************************/
   /*                                                                       */

diff in engine

diff --git a/DEPS b/DEPS
index a2703102f4..7dce29f00b 100644
--- a/DEPS
+++ b/DEPS
@@ -108,10 +108,10 @@ vars = {
   # The vulnerabiity database being used in this scan can be browsed
   # using this UI https://osv.dev/list
   # If a new dependency needs to be added, the upstream (non-mirrored)
-  # git URL for that dependency should be added to this list 
+  # git URL for that dependency should be added to this list
   # with the key-value pair being:
   # 'upstream_[dep name from last slash and before .git in URL]':'[git URL]'
-  # example: 
+  # example:
   "upstream_abseil-cpp": "https://github.com/abseil/abseil-cpp.git",
   "upstream_angle": "https://github.com/google/angle.git",
   "upstream_archive": "https://github.com/brendan-duncan/archive.git",
@@ -240,7 +240,7 @@ allowed_hosts = [
 ]

 deps = {
-  'src': 'https://github.com/flutter/buildroot.git' + '@' + '48f8487897b7f40e2744c808694535478d06ca47',
+  'src': 'https://github.com/dnfield/flutter_buildroot.git' + '@' + '7a3a1a28e833bd19785d84db87fb39ce6e896d08',

    # Fuchsia compatibility
    #
@@ -555,6 +555,9 @@ deps = {
   'src/third_party/expat':
    Var('chromium_git') + '/external/github.com/libexpat/libexpat.git' + '@' + '654d2de0da85662fcc7644a7acd7c2dd2cfb21f0',

+  'src/third_party/brotli':
+  Var('github_git') + '/google/brotli' + '@' + 'ed1995b6bda19244070ab5d331111f16f67c8054',
+
   'src/third_party/freetype2':
    Var('flutter_git') + '/third_party/freetype2' + '@' + '3bea2761290a1cbe7d8f75c1c5a7ad727f826a66',

Diff in buildroot:

diff --git a/build/secondary/third_party/brotli/BUILD.gn b/build/secondary/third_party/brotli/BUILD.gn
new file mode 100644
index 0000000..66f801e
--- /dev/null
+++ b/build/secondary/third_party/brotli/BUILD.gn
@@ -0,0 +1,130 @@
+# Copyright 2013 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+config("brotli_config") {
+  include_dirs = [ "c/include" ]
+}
+
+source_set("brotli_inc") {
+  sources = [
+    "c/include/brotli/decode.h",
+    "c/include/brotli/encode.h",
+    "c/include/brotli/port.h",
+    "c/include/brotli/shared_dictionary.h",
+    "c/include/brotli/types.h",
+  ]
+
+  public_configs = [ ":brotli_config" ]
+}
+
+source_set("brotlicommon") {
+  sources = [
+    "c/common/constants.c",
+    "c/common/constants.h",
+    "c/common/context.c",
+    "c/common/context.h",
+    "c/common/dictionary.c",
+    "c/common/dictionary.h",
+    "c/common/platform.c",
+    "c/common/platform.h",
+    "c/common/shared_dictionary.c",
+    "c/common/shared_dictionary_internal.h",
+    "c/common/transform.c",
+    "c/common/transform.h",
+    "c/common/version.h",
+  ]
+  deps = [ ":brotli_inc" ]
+
+  public_configs = [ ":brotli_config" ]
+}
+
+static_library("brotlidec") {
+  sources = [
+    "c/dec/bit_reader.c",
+    "c/dec/bit_reader.h",
+    "c/dec/decode.c",
+    "c/dec/huffman.c",
+    "c/dec/huffman.h",
+    "c/dec/prefix.h",
+    "c/dec/state.c",
+    "c/dec/state.h",
+  ]
+
+  deps = [ ":brotlicommon" ]
+
+  public_configs = [ ":brotli_config" ]
+}
+
+static_library("brotlienc") {
+  sources = [
+    "c/enc/backward_references.c",
+    "c/enc/backward_references.h",
+    "c/enc/backward_references_hq.c",
+    "c/enc/backward_references_hq.h",
+    "c/enc/backward_references_inc.h",
+    "c/enc/bit_cost.c",
+    "c/enc/bit_cost.h",
+    "c/enc/bit_cost_inc.h",
+    "c/enc/block_encoder_inc.h",
+    "c/enc/block_splitter.c",
+    "c/enc/block_splitter.h",
+    "c/enc/block_splitter_inc.h",
+    "c/enc/brotli_bit_stream.c",
+    "c/enc/brotli_bit_stream.h",
+    "c/enc/cluster.c",
+    "c/enc/cluster.h",
+    "c/enc/cluster_inc.h",
+    "c/enc/command.c",
+    "c/enc/command.h",
+    "c/enc/compound_dictionary.c",
+    "c/enc/compound_dictionary.h",
+    "c/enc/compress_fragment.c",
+    "c/enc/compress_fragment.h",
+    "c/enc/compress_fragment_two_pass.c",
+    "c/enc/compress_fragment_two_pass.h",
+    "c/enc/dictionary_hash.c",
+    "c/enc/dictionary_hash.h",
+    "c/enc/encode.c",
+    "c/enc/encoder_dict.c",
+    "c/enc/encoder_dict.h",
+    "c/enc/entropy_encode.c",
+    "c/enc/entropy_encode.h",
+    "c/enc/entropy_encode_static.h",
+    "c/enc/fast_log.c",
+    "c/enc/fast_log.h",
+    "c/enc/find_match_length.h",
+    "c/enc/hash.h",
+    "c/enc/hash_composite_inc.h",
+    "c/enc/hash_forgetful_chain_inc.h",
+    "c/enc/hash_longest_match64_inc.h",
+    "c/enc/hash_longest_match_inc.h",
+    "c/enc/hash_longest_match_quickly_inc.h",
+    "c/enc/hash_rolling_inc.h",
+    "c/enc/hash_to_binary_tree_inc.h",
+    "c/enc/histogram.c",
+    "c/enc/histogram.h",
+    "c/enc/histogram_inc.h",
+    "c/enc/literal_cost.c",
+    "c/enc/literal_cost.h",
+    "c/enc/memory.c",
+    "c/enc/memory.h",
+    "c/enc/metablock.c",
+    "c/enc/metablock.h",
+    "c/enc/metablock_inc.h",
+    "c/enc/params.h",
+    "c/enc/prefix.h",
+    "c/enc/quality.h",
+    "c/enc/ringbuffer.h",
+    "c/enc/state.h",
+    "c/enc/static_dict.c",
+    "c/enc/static_dict.h",
+    "c/enc/static_dict_lut.h",
+    "c/enc/utf8_util.c",
+    "c/enc/utf8_util.h",
+    "c/enc/write_bits.h",
+  ]
+
+  deps = [ ":brotlicommon" ]
+  public_configs = [ ":brotli_config" ]
+}
nawaf11 commented 7 months ago

Any updates after 1.5 year ^ ^ ?

Mr-Pepe commented 2 weeks ago

It seems that this got resolved by https://github.com/flutter/engine/pull/55908?

I like it but I'm curious as to why it was decided to accept the larger binary size in the end.

mdebbar commented 2 weeks ago

It seems that this got resolved by flutter/engine#55908?

I like it but I'm curious as to why it was decided to accept the larger binary size in the end.

To be clear, that PR only enables WOFF2 support on the web. The difference on web (specifically when using CanvasKit) is we don't have access to system fonts. That means we have to download extra fonts to render text and emoji. Those extra (aka fallback) fonts are huge in some cases (e.g. Emoji, Korean, Japanese).

In order to get the fallback fonts down to a reasonable size, we had to enable WOFF2 support and take the hit of adding a Brotli decompresser (~53KB) to the CanvasKit wasm file.

nawaf11 commented 2 weeks ago

I hope if possible, there's some options/config we can add it in (pubspec.yaml) or external lib to be added that will enable woff2 in Android/IOS ..

(Make it optional for developer), To only add this woff2 decoder if the developer need it in the app..