Add 'third_party/txt/' from commit '19426206958a432405c0d4b77102a67dde4d2fa1'

git-subtree-dir: third_party/txt
git-subtree-mainline: 106bbf2d3a
git-subtree-split: 1942620695
This commit is contained in:
Chinmay Garde
2017-08-25 15:16:46 -07:00
203 changed files with 26203 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
# Defines the Chromium style for automatic reformatting.
# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
BasedOnStyle: Chromium
# This defaults to 'Auto'. Explicitly set it for a while, so that
# 'vector<vector<int> >' in existing files gets formatted to
# 'vector<vector<int>>'. ('Auto' means that clang-format will only use
# 'int>>' if the file already contains at least one such instance.)
Standard: Cpp11

View File

@@ -0,0 +1,10 @@
# Auto detect text files and perform LF normalization
* text=auto
# Always perform LF normalization on these files
*.c text
*.cc text
*.cpp text
*.h text
*.gn text
*.md text

View File

@@ -0,0 +1,17 @@
*.pyc
*~
.*.sw?
.DS_Store
.classpath
.cproject
.gdb_history
.gdbinit
.landmines
.project
.pydevproject
.checkstyle
.vscode
cscope.*
Session.vim
tags
Thumbs.db

View File

@@ -0,0 +1,11 @@
cc_library_headers {
name: "libminikin_headers",
host_supported: true,
export_include_dirs: ["include"],
}
subdirs = [
"app",
"libs/minikin",
"tests",
]

View File

@@ -0,0 +1,62 @@
# Copyright 2017 Google Inc.
#
# 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.
config("txt_config") {
include_dirs = [
"//lib/txt/include",
"//third_party/harfbuzz/src",
"//lib/txt/shims",
]
}
source_set("txt") {
if (current_toolchain == host_toolchain && !is_mac) {
defines = [ "DIRECTORY_FONT_MANAGER_AVAILABLE" ]
}
if (is_android) {
defines = [ "ANDROID_FONT_MANAGER_AVAILABLE" ]
}
sources = [
"src/font_collection.cc",
"src/font_collection.h",
"src/font_skia.cc",
"src/font_skia.h",
"src/font_style.h",
"src/font_weight.h",
"src/paint_record.cc",
"src/paint_record.h",
"src/paragraph.cc",
"src/paragraph.h",
"src/paragraph_builder.cc",
"src/paragraph_builder.h",
"src/paragraph_style.cc",
"src/paragraph_style.h",
"src/styled_runs.cc",
"src/styled_runs.h",
"src/text_align.h",
"src/text_baseline.h",
"src/text_decoration.cc",
"src/text_decoration.h",
"src/text_style.cc",
"src/text_style.h",
]
public_configs = [ ":txt_config" ]
deps = [
"//lib/txt/libs/minikin",
"//third_party/skia",
]
}

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Fuchsia project.
Google hereby grants to you a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this
section) patent license to make, have made, use, offer to sell, sell,
import, transfer, and otherwise run, modify and propagate the contents
of this implementation of Fuchsia, where such license applies only to
those patent claims, both currently owned by Google and acquired in
the future, licensable by Google that are necessarily infringed by
this implementation. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute
or order or agree to the institution of patent litigation or any other
patent enforcement activity against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that this
implementation of Fuchsia constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of
Fuchsia shall terminate as of the date such litigation is filed.

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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.
// see how_to_run.txt for instructions on running these tests
cc_binary_host {
name: "hyphtool",
static_libs: ["libminikin"],
// Shared libraries which are dependencies of minikin; these are not automatically
// pulled in by the build system (and thus sadly must be repeated).
shared_libs: [
"liblog",
"libicuuc",
],
srcs: ["HyphTool.cpp"],
}

View File

@@ -0,0 +1,64 @@
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>
#include "unicode/locid.h"
#include <vector>
#include <minikin/Hyphenator.h>
using minikin::HyphenationType;
using minikin::Hyphenator;
Hyphenator* loadHybFile(const char* fn, int minPrefix, int minSuffix) {
struct stat statbuf;
int status = stat(fn, &statbuf);
if (status < 0) {
fprintf(stderr, "error opening %s\n", fn);
return nullptr;
}
size_t size = statbuf.st_size;
FILE* f = fopen(fn, "rb");
if (f == NULL) {
fprintf(stderr, "error opening %s\n", fn);
return nullptr;
}
uint8_t* buf = new uint8_t[size];
size_t read_size = fread(buf, 1, size, f);
fclose(f);
if (read_size < size) {
fprintf(stderr, "error reading %s\n", fn);
delete[] buf;
return nullptr;
}
return Hyphenator::loadBinary(buf, minPrefix, minSuffix);
}
int main(int argc, char** argv) {
Hyphenator* hyph = loadHybFile("/tmp/en.hyb", 2, 3); // should also be configurable
std::vector<HyphenationType> result;
std::vector<uint16_t> word;
if (argc < 2) {
fprintf(stderr, "usage: hyphtool word\n");
return 1;
}
char* asciiword = argv[1];
size_t len = strlen(asciiword);
for (size_t i = 0; i < len; i++) {
uint32_t c = asciiword[i];
if (c == '-') {
c = 0x00AD;
}
// ASCII (or possibly ISO Latin 1), but kinda painful to do utf conversion :(
word.push_back(c);
}
hyph->hyphenate(&result, word.data(), word.size(), icu::Locale::getUS());
for (size_t i = 0; i < len; i++) {
if (result[i] != HyphenationType::DONT_BREAK) {
printf("-");
}
printf("%c", word[i]);
}
printf("\n");
return 0;
}

View File

@@ -0,0 +1,47 @@
# Copyright 2017 Google, Inc.
#
# 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.
executable("benchmarks") {
output_name = "txt_benchmarks"
testonly = true
include_dirs = [
"//lib/txt/src",
"//lib/txt/include",
"//third_party/icu/source",
]
sources = [
"font_collection_benchmarks.cc",
"paint_record_benchmarks.cc",
"paragraph_benchmarks.cc",
"paragraph_builder_benchmarks.cc",
"styled_runs_benchmarks.cc",
"txt_run_all_benchmarks.cc",
"utils.cc",
"utils.h",
]
deps = [
"//dart/runtime:libdart_jit", # Logging
"//flutter/fml",
"//lib/txt/shims",
"//lib/txt",
"//third_party/benchmark",
"//third_party/skia",
"//third_party/harfbuzz",
"//lib/txt/libs/minikin",
]
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "third_party/benchmark/include/benchmark/benchmark_api.h"
#include "lib/ftl/command_line.h"
#include "lib/ftl/logging.h"
#include "lib/txt/src/font_collection.h"
#include "third_party/skia/include/ports/SkFontMgr.h"
#include "third_party/skia/include/ports/SkFontMgr_directory.h"
#include "utils.h"
namespace txt {
// Include this fake bench first because the first benchmark produces
// inconsistent times.
static void BM_FAKE_BENCHMARK(benchmark::State& state) {
while (state.KeepRunning()) {
continue;
}
}
BENCHMARK(BM_FAKE_BENCHMARK);
static void BM_FontCollectionCustomInit(benchmark::State& state) {
while (state.KeepRunning()) {
benchmark::DoNotOptimize(
FontCollection::GetFontCollection(txt::GetFontDir()));
}
}
BENCHMARK(BM_FontCollectionCustomInit);
static void BM_FontCollectionInit(benchmark::State& state) {
while (state.KeepRunning()) {
benchmark::DoNotOptimize(FontCollection::GetFontCollection());
}
}
BENCHMARK(BM_FontCollectionInit);
static void BM_FontCollectionSkFontMgr(benchmark::State& state) {
while (state.KeepRunning()) {
auto mgr = SkFontMgr_New_Custom_Directory(txt::GetFontDir().c_str());
}
}
BENCHMARK(BM_FontCollectionSkFontMgr);
static void BM_FontCollectionGetMinikinFontCollectionForFamily(
benchmark::State& state) {
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
while (state.KeepRunning()) {
font_collection.GetMinikinFontCollectionForFamily("Roboto");
}
}
BENCHMARK(BM_FontCollectionGetMinikinFontCollectionForFamily);
} // namespace txt

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "third_party/benchmark/include/benchmark/benchmark_api.h"
#include "lib/ftl/command_line.h"
#include "lib/ftl/logging.h"
#include "lib/txt/src/paint_record.h"
#include "lib/txt/src/text_style.h"
#include "utils.h"
namespace txt {
static void BM_PaintRecordInit(benchmark::State& state) {
TextStyle style;
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setTextSize(14);
paint.setFakeBoldText(false);
SkTextBlobBuilder builder;
builder.allocRunPos(paint, 100);
auto text_blob = builder.make();
while (state.KeepRunning()) {
PaintRecord PaintRecord(style, text_blob, SkPaint::FontMetrics(), 0, 0);
}
}
BENCHMARK(BM_PaintRecordInit);
} // namespace txt

View File

@@ -0,0 +1,442 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "third_party/benchmark/include/benchmark/benchmark_api.h"
#include <minikin/Layout.h>
#include "lib/ftl/command_line.h"
#include "lib/ftl/logging.h"
#include "lib/txt/libs/minikin/LayoutUtils.h"
#include "lib/txt/src/font_collection.h"
#include "lib/txt/src/font_skia.h"
#include "lib/txt/src/font_style.h"
#include "lib/txt/src/font_weight.h"
#include "lib/txt/src/paragraph.h"
#include "lib/txt/src/paragraph_builder.h"
#include "lib/txt/src/text_align.h"
#include "third_party/icu/source/common/unicode/unistr.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "utils.h"
namespace txt {
static void BM_ParagraphShortLayout(benchmark::State& state) {
const char* text = "Hello World";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
builder.Pop();
auto paragraph = builder.Build();
while (state.KeepRunning()) {
paragraph->SetDirty();
paragraph->Layout(300, true);
}
}
BENCHMARK(BM_ParagraphShortLayout);
static void BM_ParagraphLongLayout(benchmark::State& state) {
const char* text =
"This is a very long sentence to test if the text will properly wrap "
"around and go to the next line. Sometimes, short sentence. Longer "
"sentences are okay too because they are necessary. Very short. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum.";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
builder.Pop();
auto paragraph = builder.Build();
while (state.KeepRunning()) {
paragraph->SetDirty();
paragraph->Layout(300, true);
}
}
BENCHMARK(BM_ParagraphLongLayout);
static void BM_ParagraphJustifyLayout(benchmark::State& state) {
const char* text =
"This is a very long sentence to test if the text will properly wrap "
"around and go to the next line. Sometimes, short sentence. Longer "
"sentences are okay too because they are necessary. Very short. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum.";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
paragraph_style.text_align = TextAlign::justify;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
builder.Pop();
auto paragraph = builder.Build();
while (state.KeepRunning()) {
paragraph->SetDirty();
paragraph->Layout(300, true);
}
}
BENCHMARK(BM_ParagraphJustifyLayout);
static void BM_ParagraphManyStylesLayout(benchmark::State& state) {
const char* text = "-";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
for (int i = 0; i < 1000; ++i) {
builder.PushStyle(text_style);
builder.AddText(u16_text);
}
auto paragraph = builder.Build();
while (state.KeepRunning()) {
paragraph->SetDirty();
paragraph->Layout(300, true);
}
}
BENCHMARK(BM_ParagraphManyStylesLayout);
static void BM_ParagraphTextBigO(benchmark::State& state) {
std::vector<uint16_t> text;
for (uint16_t i = 0; i < state.range(0); ++i) {
text.push_back(i % 5 == 0 ? ' ' : i);
}
std::u16string u16_text(text.data(), text.data() + text.size());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
builder.Pop();
auto paragraph = builder.Build();
while (state.KeepRunning()) {
paragraph->SetDirty();
paragraph->Layout(300, true);
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphTextBigO)
->RangeMultiplier(4)
->Range(1 << 6, 1 << 14)
->Complexity(benchmark::oN);
static void BM_ParagraphStylesBigO(benchmark::State& state) {
const char* text = "vry shrt ";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
for (int i = 0; i < state.range(0); ++i) {
builder.PushStyle(text_style);
builder.AddText(u16_text);
}
auto paragraph = builder.Build();
while (state.KeepRunning()) {
paragraph->SetDirty();
paragraph->Layout(300, true);
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphStylesBigO)
->RangeMultiplier(4)
->Range(1 << 3, 1 << 12)
->Complexity(benchmark::oN);
static void BM_ParagraphPaintSimple(benchmark::State& state) {
const char* text = "Hello world! This is a simple sentence to test drawing.";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
auto paragraph = builder.Build();
paragraph->Layout(300, true);
std::unique_ptr<SkBitmap> bitmap = std::make_unique<SkBitmap>();
std::unique_ptr<SkCanvas> canvas = std::make_unique<SkCanvas>(*bitmap);
bitmap->allocN32Pixels(1000, 1000);
canvas->clear(SK_ColorWHITE);
int offset = 0;
while (state.KeepRunning()) {
paragraph->Paint(canvas.get(), offset % 700, 10);
offset++;
}
}
BENCHMARK(BM_ParagraphPaintSimple);
static void BM_ParagraphPaintLarge(benchmark::State& state) {
const char* text =
"Hello world! This is a simple sentence to test drawing. Hello world! "
"This is a simple sentence to test drawing. Hello world! This is a "
"simple sentence to test drawing.Hello world! This is a simple sentence "
"to test drawing. Hello world! "
"This is a simple sentence to test drawing. Hello world! This is a "
"simple sentence to test drawing.Hello world! This is a simple sentence "
"to test drawing. Hello world! "
"This is a simple sentence to test drawing. Hello world! This is a "
"simple sentence to test drawing.Hello world! This is a simple sentence "
"to test drawing. Hello world! "
"This is a simple sentence to test drawing. Hello world! This is a "
"simple sentence to test drawing.Hello world! This is a simple sentence "
"to test drawing. Hello world! "
"This is a simple sentence to test drawing. Hello world! This is a "
"simple sentence to test drawing.Hello world! This is a simple sentence "
"to test drawing. Hello world! "
"This is a simple sentence to test drawing. Hello world! This is a "
"simple sentence to test drawing.";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
auto paragraph = builder.Build();
paragraph->Layout(300, true);
std::unique_ptr<SkBitmap> bitmap = std::make_unique<SkBitmap>();
std::unique_ptr<SkCanvas> canvas = std::make_unique<SkCanvas>(*bitmap);
bitmap->allocN32Pixels(1000, 1000);
canvas->clear(SK_ColorWHITE);
int offset = 0;
while (state.KeepRunning()) {
paragraph->Paint(canvas.get(), offset % 700, 10);
offset++;
}
}
BENCHMARK(BM_ParagraphPaintLarge);
static void BM_ParagraphPaintDecoration(benchmark::State& state) {
const char* text =
"Hello world! This is a simple sentence to test drawing. Hello world! "
"This is a simple sentence to test drawing.";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.decoration = TextDecoration(0x1 | 0x2 | 0x4);
text_style.decoration_style = TextDecorationStyle(kSolid);
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
text_style.decoration_style = TextDecorationStyle(kDotted);
builder.PushStyle(text_style);
builder.AddText(u16_text);
text_style.decoration_style = TextDecorationStyle(kWavy);
builder.PushStyle(text_style);
builder.AddText(u16_text);
auto paragraph = builder.Build();
paragraph->Layout(300, true);
std::unique_ptr<SkBitmap> bitmap = std::make_unique<SkBitmap>();
std::unique_ptr<SkCanvas> canvas = std::make_unique<SkCanvas>(*bitmap);
bitmap->allocN32Pixels(1000, 1000);
canvas->clear(SK_ColorWHITE);
int offset = 0;
while (state.KeepRunning()) {
paragraph->Paint(canvas.get(), offset % 700, 10);
offset++;
}
}
BENCHMARK(BM_ParagraphPaintDecoration);
// -----------------------------------------------------------------------------
//
// The following benchmarks break down the layout function and attempts to time
// each of the components to more finely attribute latency.
//
// -----------------------------------------------------------------------------
static void BM_ParagraphMinikinDoLayout(benchmark::State& state) {
std::vector<uint16_t> text;
for (uint16_t i = 0; i < 16000 * 2; ++i) {
text.push_back(i % 5 == 0 ? ' ' : i);
}
minikin::FontStyle font;
txt::TextStyle text_style;
text_style.font_family = "Roboto";
minikin::MinikinPaint paint;
font = minikin::FontStyle(4, false);
paint.size = text_style.font_size;
paint.letterSpacing = text_style.letter_spacing;
paint.wordSpacing = text_style.word_spacing;
auto collection =
FontCollection::GetFontCollection(txt::GetFontDir())
.GetMinikinFontCollectionForFamily(text_style.font_family);
while (state.KeepRunning()) {
minikin::Layout layout;
layout.doLayout(text.data(), 0, state.range(0), state.range(0), 0, font,
paint, collection);
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphMinikinDoLayout)
->RangeMultiplier(4)
->Range(1 << 7, 1 << 14)
->Complexity(benchmark::oN);
static void BM_ParagraphMinikinAddStyleRun(benchmark::State& state) {
std::vector<uint16_t> text;
for (uint16_t i = 0; i < 16000 * 2; ++i) {
text.push_back(i % 5 == 0 ? ' ' : i);
}
minikin::FontStyle font;
txt::TextStyle text_style;
text_style.font_family = "Roboto";
minikin::MinikinPaint paint;
font = minikin::FontStyle(4, false);
paint.size = text_style.font_size;
paint.letterSpacing = text_style.letter_spacing;
paint.wordSpacing = text_style.word_spacing;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
minikin::LineBreaker breaker;
breaker.setLocale(icu::Locale(), nullptr);
breaker.resize(text.size());
memcpy(breaker.buffer(), text.data(), text.size() * sizeof(text[0]));
breaker.setText();
while (state.KeepRunning()) {
for (int i = 0; i < 20; ++i) {
breaker.addStyleRun(
&paint, font_collection.GetMinikinFontCollectionForFamily("Roboto"),
font, state.range(0) / 20 * i, state.range(0) / 20 * (i + 1), false,
0);
}
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphMinikinAddStyleRun)
->RangeMultiplier(4)
->Range(1 << 7, 1 << 14)
->Complexity(benchmark::oN);
static void BM_ParagraphSkTextBlobAlloc(benchmark::State& state) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setTextSize(14);
paint.setFakeBoldText(false);
while (state.KeepRunning()) {
SkTextBlobBuilder builder;
builder.allocRunPos(paint, state.range(0));
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphSkTextBlobAlloc)
->RangeMultiplier(4)
->Range(1 << 7, 1 << 14)
->Complexity(benchmark::oN);
} // namespace txt

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "third_party/benchmark/include/benchmark/benchmark_api.h"
#include "lib/ftl/logging.h"
#include "lib/txt/src/font_collection.h"
#include "lib/txt/src/font_style.h"
#include "lib/txt/src/font_weight.h"
#include "lib/txt/src/paragraph.h"
#include "lib/txt/src/paragraph_builder.h"
#include "lib/txt/src/text_align.h"
#include "third_party/icu/source/common/unicode/unistr.h"
#include "third_party/skia/include/core/SkColor.h"
#include "utils.h"
namespace txt {
static void BM_ParagraphBuilderConstruction(benchmark::State& state) {
txt::ParagraphStyle paragraph_style;
while (state.KeepRunning()) {
txt::ParagraphBuilder builder(paragraph_style);
}
}
BENCHMARK(BM_ParagraphBuilderConstruction);
static void BM_ParagraphBuilderPushStyle(benchmark::State& state) {
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
while (state.KeepRunning()) {
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
}
}
BENCHMARK(BM_ParagraphBuilderPushStyle);
static void BM_ParagraphBuilderPushPop(benchmark::State& state) {
txt::ParagraphStyle paragraph_style;
txt::ParagraphBuilder builder(paragraph_style);
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
while (state.KeepRunning()) {
builder.PushStyle(text_style);
builder.Pop();
}
}
BENCHMARK(BM_ParagraphBuilderPushPop);
static void BM_ParagraphBuilderAddTextString(benchmark::State& state) {
std::string text = "Hello World";
txt::ParagraphStyle paragraph_style;
while (state.KeepRunning()) {
txt::ParagraphBuilder builder(paragraph_style);
builder.AddText(text);
}
}
BENCHMARK(BM_ParagraphBuilderAddTextString);
static void BM_ParagraphBuilderAddTextChar(benchmark::State& state) {
const char* text = "Hello World";
txt::ParagraphStyle paragraph_style;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
while (state.KeepRunning()) {
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.AddText(text);
}
}
BENCHMARK(BM_ParagraphBuilderAddTextChar);
static void BM_ParagraphBuilderAddTextU16stringShort(benchmark::State& state) {
const char* text = "H";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
while (state.KeepRunning()) {
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.AddText(u16_text);
}
}
BENCHMARK(BM_ParagraphBuilderAddTextU16stringShort);
static void BM_ParagraphBuilderAddTextU16stringLong(benchmark::State& state) {
const char* text =
"This is a very long sentence to test if the text will properly wrap "
"around and go to the next line. Sometimes, short sentence. Longer "
"sentences are okay too because they are necessary. Very short. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum.";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
while (state.KeepRunning()) {
txt::ParagraphBuilder builder(paragraph_style);
builder.AddText(u16_text);
}
}
BENCHMARK(BM_ParagraphBuilderAddTextU16stringLong);
static void BM_ParagraphBuilderShortParagraphConstruct(
benchmark::State& state) {
const char* text = "Hello World";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
while (state.KeepRunning()) {
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
builder.Pop();
auto paragraph = builder.Build();
}
}
BENCHMARK(BM_ParagraphBuilderShortParagraphConstruct);
static void BM_ParagraphBuilderLongParagraphConstruct(benchmark::State& state) {
const char* text =
"This is a very long sentence to test if the text will properly wrap "
"around and go to the next line. Sometimes, short sentence. Longer "
"sentences are okay too because they are necessary. Very short. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum.";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
txt::ParagraphStyle paragraph_style;
txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
auto font_collection = FontCollection::GetFontCollection(txt::GetFontDir());
while (state.KeepRunning()) {
txt::ParagraphBuilder builder(paragraph_style, &font_collection);
builder.PushStyle(text_style);
builder.AddText(u16_text);
builder.Pop();
auto paragraph = builder.Build();
}
}
BENCHMARK(BM_ParagraphBuilderLongParagraphConstruct);
} // namespace txt

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "third_party/benchmark/include/benchmark/benchmark_api.h"
#include "lib/ftl/command_line.h"
#include "lib/ftl/logging.h"
#include "lib/txt/src/styled_runs.h"
#include "lib/txt/src/text_style.h"
#include "utils.h"
namespace txt {
static void BM_StyledRunsGetRun(benchmark::State& state) {
StyledRuns runs;
TextStyle style;
runs.AddStyle(style);
runs.StartRun(0, 0);
runs.EndRunIfNeeded(11);
while (state.KeepRunning()) {
runs.GetRun(0);
}
}
BENCHMARK(BM_StyledRunsGetRun);
} // namespace txt

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "third_party/benchmark/include/benchmark/benchmark_api.h"
#include "flutter/fml/icu_util.h"
#include "lib/ftl/logging.h"
#include "utils.h"
// We will use a custom main to allow custom font directories for consistency.
int main(int argc, char** argv) {
::benchmark::Initialize(&argc, argv);
ftl::CommandLine cmd = ftl::CommandLineFromArgcArgv(argc, argv);
txt::SetCommandLine(cmd);
std::string dir = txt::GetCommandLineForProcess().GetOptionValueWithDefault(
"font-directory", "");
txt::SetFontDir(dir);
if (txt::GetFontDir().length() <= 0) {
FTL_LOG(ERROR) << "Font directory must be specified with "
"--font-directoy=\"<directoy>\" to run this test.";
return EXIT_FAILURE;
}
FTL_DCHECK(txt::GetFontDir().length() > 0);
fml::icu::InitializeICU();
::benchmark::RunSpecifiedBenchmarks();
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include <string>
#include "lib/ftl/command_line.h"
#include "lib/txt/tests/txt/utils.h"
namespace txt {
static std::string gFontDir;
static ftl::CommandLine gCommandLine;
const std::string GetFontDir() {
return gFontDir;
}
void SetFontDir(const std::string& dir) {
gFontDir = dir;
}
const ftl::CommandLine& GetCommandLineForProcess() {
return gCommandLine;
}
void SetCommandLine(ftl::CommandLine cmd) {
gCommandLine = std::move(cmd);
}
} // namespace txt

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include <string>
#include "lib/ftl/command_line.h"
namespace txt {
const std::string GetFontDir();
void SetFontDir(const std::string& dir);
const ftl::CommandLine& GetCommandLineForProcess();
void SetCommandLine(ftl::CommandLine cmd);
} // namespace txt

View File

@@ -0,0 +1,135 @@
# Hyb (hyphenation pattern binary) file format
The hyb file format is how hyphenation patterns are stored in the system image.
Goals include:
* Concise (system image space is at a premium)
* Usable when mmap'ed, so it doesn't take significant physical RAM
* Fast to compute
* Simple
It is _not_ intended as an interchange format, so there is no attempt to make the format
extensible or facilitate backward and forward compatibility.
Further, at some point we will probably pack patterns for multiple languages into a single
physical file, to reduce number of open mmap'ed files. This document doesn't cover that.
## Theoretical basis
At heart, the file contains packed tries with suffix compression, actually quite similar
to the implementation in TeX.
The file contains three sections. The first section represents the "alphabet," including
case folding. It is effectively a map from Unicode code point to a small integer.
The second section contains the trie in packed form. It is an array of 3-tuples, packed
into a 32 bit integer. Each (suffix-compressed) trie node has a unique index within this
array, and the pattern field in the tuple is the pattern for that node. Further, each edge
in the trie has an entry in the array, and the character and link fields in the tuple
represent the label and destination of the edge. The packing strategy is as in
[Word Hy-phen-a-tion by Com-put-er](http://www.tug.org/docs/liang/liang-thesis.pdf) by
Franklin Mark Liang.
The trie representation is similar but not identical to the "double-array trie".
The fundamental operation of lookup of the edge from `s` to `t` with label `c` is
to compare `c == character[s + c]`, and if so, `t = link[s + c]`.
The third section contains the pattern strings. This section is in two parts: first,
an array with a 3-tuple for each pattern (length, number of trailing 0's, and offset
into the string pool); and second, the string pool. Each pattern is encoded as a byte
(packing 2 per byte would be possible but the space savings would not be signficant).
As much as possible of the file is represented as 32 bit integers, as that is especially
efficent to access. All are little-endian (this could be revised if the code ever needs
to be ported to big-endian systems).
## Header
```
uint32_t magic == 0x62ad7968
uint32_t version = 0
uint32_t alphabet_offset (in bytes)
uint32_t trie_offset (in bytes)
uint32_t pattern_offset (in bytes)
uint32_t file_size (in bytes)
```
Offsets are from the front of the file, and in bytes.
## Alphabet
The alphabet table comes in two versions. The first is well suited to dense Unicode
ranges and is limited to 256. The second is more general, but lookups will be slower.
### Alphabet, direct version
```
uint32_t version = 0
uint32_t min_codepoint
uint32_t max_codepoint (exclusive)
uint8_t[] data
```
The size of the data array is max_codepoint - min_codepoint. 0 represents an unmapped
character. Note that, in the current implementation, automatic hyphenation is disabled
for any word containing an unmapped character.
In general, pad bytes follow this table, aligning the next table to a 4-byte boundary.
### Alphabet, general version
```
uint32_t version = 1
uint32_t n_entries
uint32_t[n_entries] data
```
Each element in the data table is `(codepoint << 11) | value`. Note that this is
restricted to 11 bits (2048 possible values). The largest known current value is 483
(for Sanskrit).
The entries are sorted by codepoint, to facilitate binary search. Another reasonable
implementation for consumers of the data would be to build a hash table at load time.
## Trie
```
uint32_t version = 0
uint32_t char_mask
uint32_t link_shift
uint32_t link_mask
uint32_t pattern_shift
uint32_t n_entries
uint32_t[n_entries] data
```
Each element in the data table is `(pattern << pattern_shift) | (link << link_shift) | char`.
All known pattern tables fit in 32 bits total. If this is exceeded, there is a fairly
straightforward tweak, where each node occupies a slot by itself (as opposed to sharing
it with edge slots), which would require very minimal changes to the implementation (TODO
present in more detail).
## Pattern
```
uint32_t version = 0
uint32_t n_entries
uint32_t pattern_offset (in bytes)
uint32_t pattern_size (in bytes)
uint32_t[n_entries] data
uint8_t[] pattern_buf
```
Each element in data table is `(len << 26) | (shift << 20) | offset`, where an offset of 0
points to the first byte of pattern_buf.
Generally pattern_offset is `16 + 4 * n_entries`.
For example, 'a4m5ato' would be represented as `[4, 5, 0, 0, 0]`, then len = 2, shift = 3, and
offset points to [4, 5] in the pattern buffer.
Future extension: additional data representing nonstandard hyphenation. See
[Automatic non-standard hyphenation in OpenOffice.org](https://www.tug.org/TUGboat/tb27-1/tb86nemeth.pdf)
for more information about that issue.

View File

@@ -0,0 +1,28 @@
# Copyright 2017 Google Inc.
#
# 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.
executable("txt_example") {
sources = [
"main.cc",
]
deps = [
"//dart/runtime:libdart_jit",
"//flutter/fml",
"//lib/txt/libs/minikin",
"//lib/txt",
"//third_party/icu:icuuc",
"//third_party/skia",
]
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#include <unicode/unistr.h>
#include <SkCanvas.h>
#include <SkGraphics.h>
#include <SkImageEncoder.h>
#include "flutter/fml/icu_util.h"
#include "lib/txt/src/paragraph_builder.h"
namespace txt {
int runTest() {
const char* utf8_text =
"fine world that we live in is called Earth. It's pretty nice, as far as "
"I can tell. Rock, rock on. "
"\xe0\xa4\xa8\xe0\xa4\xae\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5"
"\x87";
icu::UnicodeString icu_text = icu::UnicodeString::fromUTF8(utf8_text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
ParagraphStyle paragraph_style;
ParagraphBuilder builder(paragraph_style);
TextStyle style;
style.color = SK_ColorBLUE;
style.font_size = 32.0;
builder.PushStyle(style);
builder.AddText(u16_text);
style.color = SK_ColorYELLOW;
builder.PushStyle(style);
builder.AddText(u16_text);
builder.Pop();
builder.AddText(u16_text);
builder.Pop();
auto paragraph = builder.Build();
int width = 800;
int height = 600;
paragraph->Layout(width);
SkAutoGraphics ag;
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
SkCanvas canvas(bitmap);
paragraph->Paint(&canvas, 10.0, 200.0);
SkFILEWStream file("foo.png");
return SkEncodeImage(&file, bitmap, SkEncodedImageFormat::kPNG, 100)
? EXIT_SUCCESS
: EXIT_FAILURE;
}
} // namespace txt
int main(int argc, const char** argv) {
fml::icu::InitializeICU();
return txt::runTest();
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_CMAP_COVERAGE_H
#define MINIKIN_CMAP_COVERAGE_H
#include <minikin/SparseBitSet.h>
namespace minikin {
class CmapCoverage {
public:
static SparseBitSet getCoverage(const uint8_t* cmap_data, size_t cmap_size,
bool* has_cmap_format14_subtable);
};
} // namespace minikin
#endif // MINIKIN_CMAP_COVERAGE_H

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.
*/
#include <unicode/uchar.h>
namespace minikin {
// Returns true if c is emoji.
bool isEmoji(uint32_t c);
// Returns true if c is emoji modifier base.
bool isEmojiBase(uint32_t c);
// Returns true if c is emoji modifier.
bool isEmojiModifier(uint32_t c);
// Bidi override for ICU that knows about new emoji.
UCharDirection emojiBidiOverride(const void* context, UChar32 c);
} // namespace minikin

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_FONT_COLLECTION_H
#define MINIKIN_FONT_COLLECTION_H
#include <memory>
#include <unordered_set>
#include <vector>
#include <minikin/MinikinFont.h>
#include <minikin/FontFamily.h>
namespace minikin {
class FontCollection {
public:
explicit FontCollection(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
explicit FontCollection(std::shared_ptr<FontFamily>&& typeface);
struct Run {
FakedFont fakedFont;
int start;
int end;
};
void itemize(const uint16_t *string, size_t string_length, FontStyle style,
std::vector<Run>* result) const;
// Returns true if there is a glyph for the code point and variation selector pair.
// Returns false if no fonts have a glyph for the code point and variation
// selector pair, or invalid variation selector is passed.
bool hasVariationSelector(uint32_t baseCodepoint, uint32_t variationSelector) const;
// Get base font with fakery information (fake bold could affect metrics)
FakedFont baseFontFaked(FontStyle style);
// Creates new FontCollection based on this collection while applying font variations. Returns
// nullptr if none of variations apply to this collection.
std::shared_ptr<FontCollection>
createCollectionWithVariation(const std::vector<FontVariation>& variations);
const std::unordered_set<AxisTag>& getSupportedTags() const {
return mSupportedAxes;
}
uint32_t getId() const;
private:
static const int kLogCharsPerPage = 8;
static const int kPageMask = (1 << kLogCharsPerPage) - 1;
// mFamilyVec holds the indices of the mFamilies and mRanges holds the range of indices of
// mFamilyVec. The maximum number of pages is 0x10FF (U+10FFFF >> 8). The maximum number of
// the fonts is 0xFF. Thus, technically the maximum length of mFamilyVec is 0x10EE01
// (0x10FF * 0xFF). However, in practice, 16-bit integers are enough since most fonts supports
// only limited range of code points.
struct Range {
uint16_t start;
uint16_t end;
};
// Initialize the FontCollection.
void init(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch, uint32_t vs,
uint32_t langListId, int variant) const;
uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, int variant, uint32_t langListId,
const std::shared_ptr<FontFamily>& fontFamily) const;
uint32_t calcCoverageScore(uint32_t ch, uint32_t vs,
const std::shared_ptr<FontFamily>& fontFamily) const;
static uint32_t calcLanguageMatchingScore(uint32_t userLangListId,
const FontFamily& fontFamily);
static uint32_t calcVariantMatchingScore(int variant, const FontFamily& fontFamily);
// static for allocating unique id's
static uint32_t sNextId;
// unique id for this font collection (suitable for cache key)
uint32_t mId;
// Highest UTF-32 code point that can be mapped
uint32_t mMaxChar;
// This vector has pointers to the all font family instances in this collection.
// This vector can't be empty.
std::vector<std::shared_ptr<FontFamily>> mFamilies;
// Following two vectors are pre-calculated tables for resolving coverage faster.
// For example, to iterate over all fonts which support Unicode code point U+XXYYZZ,
// iterate font families index from mFamilyVec[mRanges[0xXXYY].start] to
// mFamilyVec[mRange[0xXXYY].end] instead of whole mFamilies.
// This vector contains indices into mFamilies.
// This vector can't be empty.
std::vector<Range> mRanges;
std::vector<uint8_t> mFamilyVec;
// This vector has pointers to the font family instances which have cmap 14 subtables.
std::vector<std::shared_ptr<FontFamily>> mVSFamilyVec;
// Set of supported axes in this collection.
std::unordered_set<AxisTag> mSupportedAxes;
};
} // namespace minikin
#endif // MINIKIN_FONT_COLLECTION_H

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_FONT_FAMILY_H
#define MINIKIN_FONT_FAMILY_H
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include <hb.h>
#include <utils/TypeHelpers.h>
#include <minikin/SparseBitSet.h>
namespace minikin {
class MinikinFont;
// FontStyle represents all style information needed to select an actual font
// from a collection. The implementation is packed into two 32-bit words
// so it can be efficiently copied, embedded in other objects, etc.
class FontStyle {
public:
FontStyle() : FontStyle(0 /* variant */, 4 /* weight */, false /* italic */) {}
FontStyle(int weight, bool italic) : FontStyle(0 /* variant */, weight, italic) {}
FontStyle(uint32_t langListId) // NOLINT(implicit)
: FontStyle(langListId, 0 /* variant */, 4 /* weight */, false /* italic */) {}
FontStyle(int variant, int weight, bool italic);
FontStyle(uint32_t langListId, int variant, int weight, bool italic);
int getWeight() const { return bits & kWeightMask; }
bool getItalic() const { return (bits & kItalicMask) != 0; }
int getVariant() const { return (bits >> kVariantShift) & kVariantMask; }
uint32_t getLanguageListId() const { return mLanguageListId; }
bool operator==(const FontStyle other) const {
return bits == other.bits && mLanguageListId == other.mLanguageListId;
}
android::hash_t hash() const;
// Looks up a language list from an internal cache and returns its ID.
// If the passed language list is not in the cache, registers it and returns newly assigned ID.
static uint32_t registerLanguageList(const std::string& languages);
private:
static const uint32_t kWeightMask = (1 << 4) - 1;
static const uint32_t kItalicMask = 1 << 4;
static const int kVariantShift = 5;
static const uint32_t kVariantMask = (1 << 2) - 1;
static uint32_t pack(int variant, int weight, bool italic);
uint32_t bits;
uint32_t mLanguageListId;
};
enum FontVariant {
VARIANT_DEFAULT = 0,
VARIANT_COMPACT = 1,
VARIANT_ELEGANT = 2,
};
inline android::hash_t hash_type(const FontStyle &style) {
return style.hash();
}
// attributes representing transforms (fake bold, fake italic) to match styles
class FontFakery {
public:
FontFakery() : mFakeBold(false), mFakeItalic(false) { }
FontFakery(bool fakeBold, bool fakeItalic) : mFakeBold(fakeBold), mFakeItalic(fakeItalic) { }
// TODO: want to support graded fake bolding
bool isFakeBold() { return mFakeBold; }
bool isFakeItalic() { return mFakeItalic; }
private:
bool mFakeBold;
bool mFakeItalic;
};
struct FakedFont {
// ownership is the enclosing FontCollection
MinikinFont* font;
FontFakery fakery;
};
typedef uint32_t AxisTag;
struct Font {
Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style);
Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style);
Font(Font&& o);
Font(const Font& o);
std::shared_ptr<MinikinFont> typeface;
FontStyle style;
std::unordered_set<AxisTag> getSupportedAxesLocked() const;
};
struct FontVariation {
FontVariation(AxisTag axisTag, float value) : axisTag(axisTag), value(value) {}
AxisTag axisTag;
float value;
};
class FontFamily {
public:
explicit FontFamily(std::vector<Font>&& fonts);
FontFamily(int variant, std::vector<Font>&& fonts);
FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts);
// TODO: Good to expose FontUtil.h.
static bool analyzeStyle(const std::shared_ptr<MinikinFont>& typeface, int* weight,
bool* italic);
FakedFont getClosestMatch(FontStyle style) const;
uint32_t langId() const { return mLangId; }
int variant() const { return mVariant; }
// API's for enumerating the fonts in a family. These don't guarantee any particular order
size_t getNumFonts() const { return mFonts.size(); }
const std::shared_ptr<MinikinFont>& getFont(size_t index) const {
return mFonts[index].typeface;
}
FontStyle getStyle(size_t index) const { return mFonts[index].style; }
bool isColorEmojiFamily() const;
const std::unordered_set<AxisTag>& supportedAxes() const { return mSupportedAxes; }
// Get Unicode coverage.
const SparseBitSet& getCoverage() const { return mCoverage; }
// Returns true if the font has a glyph for the code point and variation selector pair.
// Caller should acquire a lock before calling the method.
bool hasGlyph(uint32_t codepoint, uint32_t variationSelector) const;
// Returns true if this font family has a variaion sequence table (cmap format 14 subtable).
bool hasVSTable() const { return mHasVSTable; }
// Creates new FontFamily based on this family while applying font variations. Returns nullptr
// if none of variations apply to this family.
std::shared_ptr<FontFamily> createFamilyWithVariation(
const std::vector<FontVariation>& variations) const;
private:
void computeCoverage();
uint32_t mLangId;
int mVariant;
std::vector<Font> mFonts;
std::unordered_set<AxisTag> mSupportedAxes;
SparseBitSet mCoverage;
bool mHasVSTable;
// Forbid copying and assignment.
FontFamily(const FontFamily&) = delete;
void operator=(const FontFamily&) = delete;
};
} // namespace minikin
#endif // MINIKIN_FONT_FAMILY_H

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_GRAPHEME_BREAK_H
#define MINIKIN_GRAPHEME_BREAK_H
namespace minikin {
class GraphemeBreak {
public:
// These values must be kept in sync with CURSOR_AFTER etc in Paint.java
enum MoveOpt {
AFTER = 0,
AT_OR_AFTER = 1,
BEFORE = 2,
AT_OR_BEFORE = 3,
AT = 4
};
// Determine whether the given offset is a grapheme break.
// This implementation generally follows Unicode's UTR #29 extended
// grapheme break, with various tweaks.
static bool isGraphemeBreak(const float* advances, const uint16_t* buf, size_t start,
size_t count, size_t offset);
// Matches Android's Java API. Note, return (size_t)-1 for AT to
// signal non-break because unsigned return type.
static size_t getTextRunCursor(const float* advances, const uint16_t* buf, size_t start,
size_t count, size_t offset, MoveOpt opt);
};
} // namespace minikin
#endif // MINIKIN_GRAPHEME_BREAK_H

View File

@@ -0,0 +1,178 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
/**
* An implementation of Liang's hyphenation algorithm.
*/
#ifndef U_USING_ICU_NAMESPACE
#define U_USING_ICU_NAMESPACE 0
#endif // U_USING_ICU_NAMESPACE
#include "unicode/locid.h"
#include <memory>
#include <unordered_map>
#include <vector>
#ifndef MINIKIN_HYPHENATOR_H
#define MINIKIN_HYPHENATOR_H
namespace minikin {
enum class HyphenationType : uint8_t {
// Note: There are implicit assumptions scattered in the code that DONT_BREAK is 0.
// Do not break.
DONT_BREAK = 0,
// Break the line and insert a normal hyphen.
BREAK_AND_INSERT_HYPHEN = 1,
// Break the line and insert an Armenian hyphen (U+058A).
BREAK_AND_INSERT_ARMENIAN_HYPHEN = 2,
// Break the line and insert a maqaf (Hebrew hyphen, U+05BE).
BREAK_AND_INSERT_MAQAF = 3,
// Break the line and insert a Canadian Syllabics hyphen (U+1400).
BREAK_AND_INSERT_UCAS_HYPHEN = 4,
// Break the line, but don't insert a hyphen. Used for cases when there is already a hyphen
// present or the script does not use a hyphen (e.g. in Malayalam).
BREAK_AND_DONT_INSERT_HYPHEN = 5,
// Break and replace the last code unit with hyphen. Used for Catalan "l·l" which hyphenates
// as "l-/l".
BREAK_AND_REPLACE_WITH_HYPHEN = 6,
// Break the line, and repeat the hyphen (which is the last character) at the beginning of the
// next line. Used in Polish, where "czerwono-niebieska" should hyphenate as
// "czerwono-/-niebieska".
BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE = 7,
// Break the line, insert a ZWJ and hyphen at the first line, and a ZWJ at the second line.
// This is used in Arabic script, mostly for writing systems of Central Asia. It's our default
// behavior when a soft hyphen is used in Arabic script.
BREAK_AND_INSERT_HYPHEN_AND_ZWJ = 8
};
// The hyphen edit represents an edit to the string when a word is
// hyphenated. The most common hyphen edit is adding a "-" at the end
// of a syllable, but nonstandard hyphenation allows for more choices.
// Note that a HyphenEdit can hold two types of edits at the same time,
// One at the beginning of the string/line and one at the end.
class HyphenEdit {
public:
static const uint32_t NO_EDIT = 0x00;
static const uint32_t INSERT_HYPHEN_AT_END = 0x01;
static const uint32_t INSERT_ARMENIAN_HYPHEN_AT_END = 0x02;
static const uint32_t INSERT_MAQAF_AT_END = 0x03;
static const uint32_t INSERT_UCAS_HYPHEN_AT_END = 0x04;
static const uint32_t INSERT_ZWJ_AND_HYPHEN_AT_END = 0x05;
static const uint32_t REPLACE_WITH_HYPHEN_AT_END = 0x06;
static const uint32_t BREAK_AT_END = 0x07;
static const uint32_t INSERT_HYPHEN_AT_START = 0x01 << 3;
static const uint32_t INSERT_ZWJ_AT_START = 0x02 << 3;
static const uint32_t BREAK_AT_START = 0x03 << 3;
// Keep in sync with the definitions in the Java code at:
// frameworks/base/graphics/java/android/graphics/Paint.java
static const uint32_t MASK_END_OF_LINE = 0x07;
static const uint32_t MASK_START_OF_LINE = 0x03 << 3;
inline static bool isReplacement(uint32_t hyph) {
return hyph == REPLACE_WITH_HYPHEN_AT_END;
}
inline static bool isInsertion(uint32_t hyph) {
return (hyph == INSERT_HYPHEN_AT_END
|| hyph == INSERT_ARMENIAN_HYPHEN_AT_END
|| hyph == INSERT_MAQAF_AT_END
|| hyph == INSERT_UCAS_HYPHEN_AT_END
|| hyph == INSERT_ZWJ_AND_HYPHEN_AT_END
|| hyph == INSERT_HYPHEN_AT_START
|| hyph == INSERT_ZWJ_AT_START);
}
const static uint32_t* getHyphenString(uint32_t hyph);
static uint32_t editForThisLine(HyphenationType type);
static uint32_t editForNextLine(HyphenationType type);
HyphenEdit() : hyphen(NO_EDIT) { }
HyphenEdit(uint32_t hyphenInt) : hyphen(hyphenInt) { } // NOLINT(implicit)
uint32_t getHyphen() const { return hyphen; }
bool operator==(const HyphenEdit &other) const { return hyphen == other.hyphen; }
uint32_t getEnd() const { return hyphen & MASK_END_OF_LINE; }
uint32_t getStart() const { return hyphen & MASK_START_OF_LINE; }
private:
uint32_t hyphen;
};
// hyb file header; implementation details are in the .cpp file
struct Header;
class Hyphenator {
public:
// Compute the hyphenation of a word, storing the hyphenation in result vector. Each entry in
// the vector is a "hyphenation type" for a potential hyphenation that can be applied at the
// corresponding code unit offset in the word.
//
// Example: word is "hyphen", result is the following, corresponding to "hy-phen":
// [DONT_BREAK, DONT_BREAK, BREAK_AND_INSERT_HYPHEN, DONT_BREAK, DONT_BREAK, DONT_BREAK]
void hyphenate(std::vector<HyphenationType>* result, const uint16_t* word, size_t len,
const icu::Locale& locale);
// Returns true if the codepoint is like U+2010 HYPHEN in line breaking and usage: a character
// immediately after which line breaks are allowed, but words containing it should not be
// automatically hyphenated.
static bool isLineBreakingHyphen(uint32_t cp);
// pattern data is in binary format, as described in doc/hyb_file_format.md. Note:
// the caller is responsible for ensuring that the lifetime of the pattern data is
// at least as long as the Hyphenator object.
// Note: nullptr is valid input, in which case the hyphenator only processes soft hyphens.
static Hyphenator* loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix);
private:
// apply various hyphenation rules including hard and soft hyphens, ignoring patterns
void hyphenateWithNoPatterns(HyphenationType* result, const uint16_t* word, size_t len,
const icu::Locale& locale);
// Try looking up word in alphabet table, return DONT_BREAK if any code units fail to map.
// Otherwise, returns BREAK_AND_INSERT_HYPHEN, BREAK_AND_INSERT_ARMENIAN_HYPHEN, or
// BREAK_AND_DONT_INSERT_HYPHEN based on the the script of the characters seen.
// Note that this method writes len+2 entries into alpha_codes (including start and stop)
HyphenationType alphabetLookup(uint16_t* alpha_codes, const uint16_t* word, size_t len);
// calculate hyphenation from patterns, assuming alphabet lookup has already been done
void hyphenateFromCodes(HyphenationType* result, const uint16_t* codes, size_t len,
HyphenationType hyphenValue);
// See also LONGEST_HYPHENATED_WORD in LineBreaker.cpp. Here the constant is used so
// that temporary buffers can be stack-allocated without waste, which is a slightly
// different use case. It measures UTF-16 code units.
static const size_t MAX_HYPHENATED_SIZE = 64;
const uint8_t* patternData;
size_t minPrefix, minSuffix;
// accessors for binary data
const Header* getHeader() const {
return reinterpret_cast<const Header*>(patternData);
}
};
} // namespace minikin
#endif // MINIKIN_HYPHENATOR_H

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_LAYOUT_H
#define MINIKIN_LAYOUT_H
#include <hb.h>
#include <memory>
#include <vector>
#include <minikin/FontCollection.h>
namespace minikin {
struct LayoutGlyph {
// index into mFaces and mHbFonts vectors. We could imagine
// moving this into a run length representation, because it's
// more efficient for long strings, and we'll probably need
// something like that for paint attributes (color, underline,
// fake b/i, etc), as having those per-glyph is bloated.
int font_ix;
unsigned int glyph_id;
float x;
float y;
};
// Internal state used during layout operation
struct LayoutContext;
enum {
kBidi_LTR = 0,
kBidi_RTL = 1,
kBidi_Default_LTR = 2,
kBidi_Default_RTL = 3,
kBidi_Force_LTR = 4,
kBidi_Force_RTL = 5,
kBidi_Mask = 0x7
};
// Lifecycle and threading assumptions for Layout:
// The object is assumed to be owned by a single thread; multiple threads
// may not mutate it at the same time.
class Layout {
public:
Layout() : mGlyphs(), mAdvances(), mFaces(), mAdvance(0), mBounds() {
mBounds.setEmpty();
}
Layout(Layout&& layout) = default;
// Forbid copying and assignment.
Layout(const Layout&) = delete;
void operator=(const Layout&) = delete;
void dump() const;
void doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
const std::shared_ptr<FontCollection>& collection);
static float measureText(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
const std::shared_ptr<FontCollection>& collection, float* advances);
// public accessors
size_t nGlyphs() const;
const MinikinFont* getFont(int i) const;
FontFakery getFakery(int i) const;
unsigned int getGlyphId(int i) const;
float getX(int i) const;
float getY(int i) const;
float getAdvance() const;
// Get advances, copying into caller-provided buffer. The size of this
// buffer must match the length of the string (count arg to doLayout).
void getAdvances(float* advances);
// The i parameter is an offset within the buf relative to start, it is < count, where
// start and count are the parameters to doLayout
float getCharAdvance(size_t i) const { return mAdvances[i]; }
void getBounds(MinikinRect* rect) const;
// Purge all caches, useful in low memory conditions
static void purgeCaches();
private:
friend class LayoutCacheKey;
// Find a face in the mFaces vector, or create a new entry
int findFace(const FakedFont& face, LayoutContext* ctx);
// Clears layout, ready to be used again
void reset();
// Lay out a single bidi run
// When layout is not null, layout info will be stored in the object.
// When advances is not null, measurement results will be stored in the array.
static float doLayoutRunCached(const uint16_t* buf, size_t runStart, size_t runLength,
size_t bufSize, bool isRtl, LayoutContext* ctx, size_t dstStart,
const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances);
// Lay out a single word
static float doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
bool isRtl, LayoutContext* ctx, size_t bufStart,
const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances);
// Lay out a single bidi run
void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
bool isRtl, LayoutContext* ctx, const std::shared_ptr<FontCollection>& collection);
// Append another layout (for example, cached value) into this one
void appendLayout(Layout* src, size_t start, float extraAdvance);
std::vector<LayoutGlyph> mGlyphs;
std::vector<float> mAdvances;
std::vector<FakedFont> mFaces;
float mAdvance;
MinikinRect mBounds;
};
} // namespace minikin
#endif // MINIKIN_LAYOUT_H

View File

@@ -0,0 +1,265 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
/**
* A module for breaking paragraphs into lines, supporting high quality
* hyphenation and justification.
*/
#ifndef MINIKIN_LINE_BREAKER_H
#define MINIKIN_LINE_BREAKER_H
#ifndef U_USING_ICU_NAMESPACE
#define U_USING_ICU_NAMESPACE 0
#endif // U_USING_ICU_NAMESPACE
#include "unicode/brkiter.h"
#include "unicode/locid.h"
#include <cmath>
#include <vector>
#include "minikin/FontCollection.h"
#include "minikin/Hyphenator.h"
#include "minikin/MinikinFont.h"
#include "minikin/WordBreaker.h"
namespace minikin {
enum BreakStrategy {
kBreakStrategy_Greedy = 0,
kBreakStrategy_HighQuality = 1,
kBreakStrategy_Balanced = 2
};
enum HyphenationFrequency {
kHyphenationFrequency_None = 0,
kHyphenationFrequency_Normal = 1,
kHyphenationFrequency_Full = 2
};
// TODO: want to generalize to be able to handle array of line widths
class LineWidths {
public:
void setWidths(float firstWidth, int firstWidthLineCount, float restWidth) {
mFirstWidth = firstWidth;
mFirstWidthLineCount = firstWidthLineCount;
mRestWidth = restWidth;
}
void setIndents(const std::vector<float>& indents) {
mIndents = indents;
}
bool isConstant() const {
// technically mFirstWidthLineCount == 0 would count too, but doesn't actually happen
return mRestWidth == mFirstWidth && mIndents.empty();
}
float getLineWidth(int line) const {
float width = (line < mFirstWidthLineCount) ? mFirstWidth : mRestWidth;
if (!mIndents.empty()) {
if ((size_t)line < mIndents.size()) {
width -= mIndents[line];
} else {
width -= mIndents.back();
}
}
return width;
}
void clear() {
mIndents.clear();
}
private:
float mFirstWidth;
int mFirstWidthLineCount;
float mRestWidth;
std::vector<float> mIndents;
};
class TabStops {
public:
void set(const int* stops, size_t nStops, int tabWidth) {
if (stops != nullptr) {
mStops.assign(stops, stops + nStops);
} else {
mStops.clear();
}
mTabWidth = tabWidth;
}
float nextTab(float widthSoFar) const {
for (size_t i = 0; i < mStops.size(); i++) {
if (mStops[i] > widthSoFar) {
return mStops[i];
}
}
return floor(widthSoFar / mTabWidth + 1) * mTabWidth;
}
private:
std::vector<int> mStops;
int mTabWidth;
};
class LineBreaker {
public:
const static int kTab_Shift = 29; // keep synchronized with TAB_MASK in StaticLayout.java
// Note: Locale persists across multiple invocations (it is not cleaned up by finish()),
// explicitly to avoid the cost of creating ICU BreakIterator objects. It should always
// be set on the first invocation, but callers are encouraged not to call again unless
// locale has actually changed.
// That logic could be here but it's better for performance that it's upstream because of
// the cost of constructing and comparing the ICU Locale object.
// Note: caller is responsible for managing lifetime of hyphenator
void setLocale(const icu::Locale& locale, Hyphenator* hyphenator);
void resize(size_t size) {
mTextBuf.resize(size);
mCharWidths.resize(size);
}
size_t size() const {
return mTextBuf.size();
}
uint16_t* buffer() {
return mTextBuf.data();
}
float* charWidths() {
return mCharWidths.data();
}
// set text to current contents of buffer
void setText();
void setLineWidths(float firstWidth, int firstWidthLineCount, float restWidth);
void setIndents(const std::vector<float>& indents);
void setTabStops(const int* stops, size_t nStops, int tabWidth) {
mTabStops.set(stops, nStops, tabWidth);
}
BreakStrategy getStrategy() const { return mStrategy; }
void setStrategy(BreakStrategy strategy) { mStrategy = strategy; }
void setJustified(bool justified) { mJustified = justified; }
HyphenationFrequency getHyphenationFrequency() const { return mHyphenationFrequency; }
void setHyphenationFrequency(HyphenationFrequency frequency) {
mHyphenationFrequency = frequency;
}
// TODO: this class is actually fairly close to being general and not tied to using
// Minikin to do the shaping of the strings. The main thing that would need to be changed
// is having some kind of callback (or virtual class, or maybe even template), which could
// easily be instantiated with Minikin's Layout. Future work for when needed.
float addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface,
FontStyle style, size_t start, size_t end, bool isRtl, double letterSpacing = 0);
void addReplacement(size_t start, size_t end, float width);
size_t computeBreaks();
const int* getBreaks() const {
return mBreaks.data();
}
const float* getWidths() const {
return mWidths.data();
}
const int* getFlags() const {
return mFlags.data();
}
void finish();
private:
// ParaWidth is used to hold cumulative width from beginning of paragraph. Note that for
// very large paragraphs, accuracy could degrade using only 32-bit float. Note however
// that float is used extensively on the Java side for this. This is a typedef so that
// we can easily change it based on performance/accuracy tradeoff.
typedef double ParaWidth;
// A single candidate break
struct Candidate {
size_t offset; // offset to text buffer, in code units
size_t prev; // index to previous break
ParaWidth preBreak; // width of text until this point, if we decide to not break here
ParaWidth postBreak; // width of text until this point, if we decide to break here
float penalty; // penalty of this break (for example, hyphen penalty)
float score; // best score found for this break
size_t lineNumber; // only updated for non-constant line widths
size_t preSpaceCount; // preceding space count before breaking
size_t postSpaceCount; // preceding space count after breaking
HyphenationType hyphenType;
};
float currentLineWidth() const;
void addWordBreak(size_t offset, ParaWidth preBreak, ParaWidth postBreak,
size_t preSpaceCount, size_t postSpaceCount, float penalty, HyphenationType hyph);
void addCandidate(Candidate cand);
void pushGreedyBreak();
// push an actual break to the output. Takes care of setting flags for tab
void pushBreak(int offset, float width, uint8_t hyphenEdit);
float getSpaceWidth() const;
void computeBreaksGreedy();
void computeBreaksOptimal(bool isRectangular);
void finishBreaksOptimal();
WordBreaker mWordBreaker;
icu::Locale mLocale;
std::vector<uint16_t>mTextBuf;
std::vector<float>mCharWidths;
Hyphenator* mHyphenator;
std::vector<HyphenationType> mHyphBuf;
// layout parameters
BreakStrategy mStrategy = kBreakStrategy_Greedy;
HyphenationFrequency mHyphenationFrequency = kHyphenationFrequency_Normal;
bool mJustified;
LineWidths mLineWidths;
TabStops mTabStops;
// result of line breaking
std::vector<int> mBreaks;
std::vector<float> mWidths;
std::vector<int> mFlags;
ParaWidth mWidth = 0;
std::vector<Candidate> mCandidates;
float mLinePenalty = 0.0f;
// the following are state for greedy breaker (updated while adding style runs)
size_t mLastBreak;
size_t mBestBreak;
float mBestScore;
ParaWidth mPreBreak; // prebreak of last break
uint32_t mLastHyphenation; // hyphen edit of last break kept for next line
int mFirstTabIndex;
size_t mSpaceCount;
};
} // namespace minikin
#endif // MINIKIN_LINE_BREAKER_H

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_MEASUREMENT_H
#define MINIKIN_MEASUREMENT_H
#include <minikin/Layout.h>
namespace minikin {
float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
size_t offset);
size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
float advance);
} // namespace minikin
#endif // MINIKIN_MEASUREMENT_H

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_FONT_H
#define MINIKIN_FONT_H
#include <string>
#include <memory>
#include <minikin/FontFamily.h>
#include <minikin/Hyphenator.h>
// An abstraction for platform fonts, allowing Minikin to be used with
// multiple actual implementations of fonts.
namespace minikin {
class MinikinFont;
// Possibly move into own .h file?
// Note: if you add a field here, either add it to LayoutCacheKey or to skipCache()
struct MinikinPaint {
MinikinPaint() : font(nullptr), size(0), scaleX(0), skewX(0), letterSpacing(0), wordSpacing(0),
paintFlags(0), fakery(), hyphenEdit(), fontFeatureSettings() { }
bool skipCache() const {
return !fontFeatureSettings.empty();
}
MinikinFont *font;
float size;
float scaleX;
float skewX;
float letterSpacing;
float wordSpacing;
uint32_t paintFlags;
FontFakery fakery;
HyphenEdit hyphenEdit;
std::string fontFeatureSettings;
};
// Only a few flags affect layout, but those that do should have values
// consistent with Android's paint flags.
enum MinikinPaintFlags {
LinearTextFlag = 0x40,
};
struct MinikinRect {
float mLeft, mTop, mRight, mBottom;
bool isEmpty() const {
return mLeft == mRight || mTop == mBottom;
}
void set(const MinikinRect& r) {
mLeft = r.mLeft;
mTop = r.mTop;
mRight = r.mRight;
mBottom = r.mBottom;
}
void offset(float dx, float dy) {
mLeft += dx;
mTop += dy;
mRight += dx;
mBottom += dy;
}
void setEmpty() {
mLeft = mTop = mRight = mBottom = 0;
}
void join(const MinikinRect& r);
};
// Callback for freeing data
typedef void (*MinikinDestroyFunc) (void* data);
class MinikinFont {
public:
explicit MinikinFont(int32_t uniqueId) : mUniqueId(uniqueId) {}
virtual ~MinikinFont();
virtual float GetHorizontalAdvance(uint32_t glyph_id,
const MinikinPaint &paint) const = 0;
virtual void GetBounds(MinikinRect* bounds, uint32_t glyph_id,
const MinikinPaint &paint) const = 0;
virtual hb_face_t* CreateHarfBuzzFace() const {
return nullptr;
}
virtual const std::vector<minikin::FontVariation>& GetAxes() const = 0;
virtual std::shared_ptr<MinikinFont> createFontWithVariation(
const std::vector<FontVariation>&) const {
return nullptr;
}
static uint32_t MakeTag(char c1, char c2, char c3, char c4) {
return ((uint32_t)c1 << 24) | ((uint32_t)c2 << 16) |
((uint32_t)c3 << 8) | (uint32_t)c4;
}
int32_t GetUniqueId() const { return mUniqueId; }
private:
const int32_t mUniqueId;
};
} // namespace minikin
#endif // MINIKIN_FONT_H

View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_SPARSE_BIT_SET_H
#define MINIKIN_SPARSE_BIT_SET_H
#include <stdint.h>
#include <sys/types.h>
#include <memory>
// ---------------------------------------------------------------------------
namespace minikin {
// This is an implementation of a set of integers. It is optimized for
// values that are somewhat sparse, in the ballpark of a maximum value
// of thousands to millions. It is particularly efficient when there are
// large gaps. The motivating example is Unicode coverage of a font, but
// the abstraction itself is fully general.
class SparseBitSet {
public:
// Create an empty bit set.
SparseBitSet() : mMaxVal(0) {}
// Initialize the set to a new value, represented by ranges. For
// simplicity, these ranges are arranged as pairs of values,
// inclusive of start, exclusive of end, laid out in a uint32 array.
SparseBitSet(const uint32_t* ranges, size_t nRanges) : SparseBitSet() {
initFromRanges(ranges, nRanges);
}
SparseBitSet(SparseBitSet&&) = default;
SparseBitSet& operator=(SparseBitSet&&) = default;
// Determine whether the value is included in the set
bool get(uint32_t ch) const {
if (ch >= mMaxVal) return false;
const uint32_t *bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]];
uint32_t index = ch & kPageMask;
return (bitmap[index >> kLogBitsPerEl] & (kElFirst >> (index & kElMask))) != 0;
}
// One more than the maximum value in the set, or zero if empty
uint32_t length() const {
return mMaxVal;
}
// The next set bit starting at fromIndex, inclusive, or kNotFound
// if none exists.
uint32_t nextSetBit(uint32_t fromIndex) const;
static const uint32_t kNotFound = ~0u;
private:
void initFromRanges(const uint32_t* ranges, size_t nRanges);
static const uint32_t kMaximumCapacity = 0xFFFFFF;
static const int kLogValuesPerPage = 8;
static const int kPageMask = (1 << kLogValuesPerPage) - 1;
static const int kLogBytesPerEl = 2;
static const int kLogBitsPerEl = kLogBytesPerEl + 3;
static const int kElMask = (1 << kLogBitsPerEl) - 1;
// invariant: sizeof(element) == (1 << kLogBytesPerEl)
typedef uint32_t element;
static const element kElAllOnes = ~((element)0);
static const element kElFirst = ((element)1) << kElMask;
static const uint16_t noZeroPage = 0xFFFF;
static uint32_t calcNumPages(const uint32_t* ranges, size_t nRanges);
static int CountLeadingZeros(element x);
uint32_t mMaxVal;
std::unique_ptr<uint16_t[]> mIndices;
std::unique_ptr<element[]> mBitmaps;
uint16_t mZeroPageIndex;
// Forbid copy and assign.
SparseBitSet(const SparseBitSet&) = delete;
void operator=(const SparseBitSet&) = delete;
};
} // namespace minikin
#endif // MINIKIN_SPARSE_BIT_SET_H

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
/**
* A wrapper around ICU's line break iterator, that gives customized line
* break opportunities, as well as identifying words for the purpose of
* hyphenation.
*/
#ifndef MINIKIN_WORD_BREAKER_H
#define MINIKIN_WORD_BREAKER_H
#include "unicode/brkiter.h"
#include <memory>
namespace minikin {
class WordBreaker {
public:
~WordBreaker() {
finish();
}
void setLocale(const icu::Locale& locale);
void setText(const uint16_t* data, size_t size);
// Advance iterator to next word break. Return offset, or -1 if EOT
ssize_t next();
// Current offset of iterator, equal to 0 at BOT or last return from next()
ssize_t current() const;
// After calling next(), wordStart() and wordEnd() are offsets defining the previous
// word. If wordEnd <= wordStart, it's not a word for the purpose of hyphenation.
ssize_t wordStart() const;
ssize_t wordEnd() const;
int breakBadness() const;
void finish();
private:
int32_t iteratorNext();
void detectEmailOrUrl();
ssize_t findNextBreakInEmailOrUrl();
std::unique_ptr<icu::BreakIterator> mBreakIterator;
UText mUText = UTEXT_INITIALIZER;
const uint16_t* mText = nullptr;
size_t mTextSize;
ssize_t mLast;
ssize_t mCurrent;
bool mIteratorWasReset;
// state for the email address / url detector
ssize_t mScanOffset;
bool mInEmailOrUrl;
};
} // namespace minikin
#endif // MINIKIN_WORD_BREAKER_H

View File

@@ -0,0 +1,86 @@
// Copyright (C) 2013 The Android Open Source Project
//
// 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.
cc_library_headers {
name: "libminikin-headers-for-tests",
export_include_dirs: ["."],
shared_libs: ["libharfbuzz_ng"],
export_shared_lib_headers: ["libharfbuzz_ng"],
}
cc_library {
name: "libminikin",
host_supported: true,
srcs: [
"Hyphenator.cpp",
],
target: {
android: {
srcs: [
"CmapCoverage.cpp",
"Emoji.cpp",
"FontCollection.cpp",
"FontFamily.cpp",
"FontLanguage.cpp",
"FontLanguageListCache.cpp",
"FontUtils.cpp",
"GraphemeBreak.cpp",
"HbFontCache.cpp",
"Layout.cpp",
"LayoutUtils.cpp",
"LineBreaker.cpp",
"Measurement.cpp",
"MinikinInternal.cpp",
"MinikinFont.cpp",
"SparseBitSet.cpp",
"WordBreaker.cpp",
],
shared_libs: [
"libharfbuzz_ng",
"libft2",
"libz",
"libutils",
],
// TODO: clean up Minikin so it doesn't need the freetype include
export_shared_lib_headers: ["libft2"],
},
},
cppflags: [
"-Werror",
"-Wall",
"-Wextra",
],
product_variables: {
debuggable: {
// Enable race detection on eng and userdebug build.
cppflags: ["-DENABLE_RACE_DETECTION"],
},
},
shared_libs: [
"liblog",
"libicuuc",
],
header_libs: ["libminikin_headers"],
export_header_lib_headers: ["libminikin_headers"],
export_shared_lib_headers: ["libicuuc"],
clang: true,
sanitize: {
misc_undefined: [
"signed-integer-overflow",
// b/26432628.
//"unsigned-integer-overflow",
],
},
}

View File

@@ -0,0 +1,61 @@
# Copyright 2017 Google Inc.
#
# 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.
config("minikin_config") {
include_dirs = [ "../../include" ]
}
source_set("minikin") {
defines = [ "WIP_NEEDS_ICU_UPDATE" ]
sources = [
"CmapCoverage.cpp",
"Emoji.cpp",
"FontCollection.cpp",
"FontFamily.cpp",
"FontLanguage.cpp",
"FontLanguage.h",
"FontLanguageListCache.cpp",
"FontLanguageListCache.h",
"FontUtils.cpp",
"FontUtils.h",
"GraphemeBreak.cpp",
"HbFontCache.cpp",
"HbFontCache.h",
"Hyphenator.cpp",
"Layout.cpp",
"LayoutUtils.cpp",
"LayoutUtils.h",
"LineBreaker.cpp",
"Measurement.cpp",
"MinikinFont.cpp",
"MinikinInternal.cpp",
"MinikinInternal.h",
"SparseBitSet.cpp",
"WordBreaker.cpp",
]
public_configs = [ ":minikin_config" ]
public_deps = [
"//lib/txt/shims",
"//third_party/freetype2",
"//third_party/harfbuzz",
"//third_party/icu:icuuc",
]
deps = [
"//third_party/zlib",
]
}

View File

@@ -0,0 +1,288 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.
*/
// Determine coverage of font given its raw "cmap" OpenType table
#define LOG_TAG "Minikin"
#include <vector>
using std::vector;
#include <log/log.h>
#include <minikin/SparseBitSet.h>
#include <minikin/CmapCoverage.h>
#include "MinikinInternal.h"
namespace minikin {
// These could perhaps be optimized to use __builtin_bswap16 and friends.
static uint32_t readU16(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 8 | ((uint32_t)data[offset + 1]);
}
static uint32_t readU32(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
}
static void addRange(vector<uint32_t> &coverage, uint32_t start, uint32_t end) {
#ifdef VERBOSE_DEBUG
ALOGD("adding range %d-%d\n", start, end);
#endif
if (coverage.empty() || coverage.back() < start) {
coverage.push_back(start);
coverage.push_back(end);
} else {
coverage.back() = end;
}
}
// Get the coverage information out of a Format 4 subtable, storing it in the coverage vector
static bool getCoverageFormat4(vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
const size_t kSegCountOffset = 6;
const size_t kEndCountOffset = 14;
const size_t kHeaderSize = 16;
const size_t kSegmentSize = 8; // total size of array elements for one segment
if (kEndCountOffset > size) {
return false;
}
size_t segCount = readU16(data, kSegCountOffset) >> 1;
if (kHeaderSize + segCount * kSegmentSize > size) {
return false;
}
for (size_t i = 0; i < segCount; i++) {
uint32_t end = readU16(data, kEndCountOffset + 2 * i);
uint32_t start = readU16(data, kHeaderSize + 2 * (segCount + i));
if (end < start) {
// invalid segment range: size must be positive
android_errorWriteLog(0x534e4554, "26413177");
return false;
}
uint32_t rangeOffset = readU16(data, kHeaderSize + 2 * (3 * segCount + i));
if (rangeOffset == 0) {
uint32_t delta = readU16(data, kHeaderSize + 2 * (2 * segCount + i));
if (((end + delta) & 0xffff) > end - start) {
addRange(coverage, start, end + 1);
} else {
for (uint32_t j = start; j < end + 1; j++) {
if (((j + delta) & 0xffff) != 0) {
addRange(coverage, j, j + 1);
}
}
}
} else {
for (uint32_t j = start; j < end + 1; j++) {
uint32_t actualRangeOffset = kHeaderSize + 6 * segCount + rangeOffset +
(i + j - start) * 2;
if (actualRangeOffset + 2 > size) {
// invalid rangeOffset is considered a "warning" by OpenType Sanitizer
continue;
}
uint32_t glyphId = readU16(data, actualRangeOffset);
if (glyphId != 0) {
addRange(coverage, j, j + 1);
}
}
}
}
return true;
}
// Get the coverage information out of a Format 12 subtable, storing it in the coverage vector
static bool getCoverageFormat12(vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
const size_t kNGroupsOffset = 12;
const size_t kFirstGroupOffset = 16;
const size_t kGroupSize = 12;
const size_t kStartCharCodeOffset = 0;
const size_t kEndCharCodeOffset = 4;
const size_t kMaxNGroups = 0xfffffff0 / kGroupSize; // protection against overflow
// For all values < kMaxNGroups, kFirstGroupOffset + nGroups * kGroupSize fits in 32 bits.
if (kFirstGroupOffset > size) {
return false;
}
uint32_t nGroups = readU32(data, kNGroupsOffset);
if (nGroups >= kMaxNGroups || kFirstGroupOffset + nGroups * kGroupSize > size) {
android_errorWriteLog(0x534e4554, "25645298");
return false;
}
for (uint32_t i = 0; i < nGroups; i++) {
uint32_t groupOffset = kFirstGroupOffset + i * kGroupSize;
uint32_t start = readU32(data, groupOffset + kStartCharCodeOffset);
uint32_t end = readU32(data, groupOffset + kEndCharCodeOffset);
if (end < start) {
// invalid group range: size must be positive
android_errorWriteLog(0x534e4554, "26413177");
return false;
}
// No need to read outside of Unicode code point range.
if (start > MAX_UNICODE_CODE_POINT) {
return true;
}
if (end > MAX_UNICODE_CODE_POINT) {
// file is inclusive, vector is exclusive
addRange(coverage, start, MAX_UNICODE_CODE_POINT + 1);
return true;
}
addRange(coverage, start, end + 1); // file is inclusive, vector is exclusive
}
return true;
}
// Lower value has higher priority. 0 for the highest priority table.
// kLowestPriority for unsupported tables.
// This order comes from HarfBuzz's hb-ot-font.cc and needs to be kept in sync with it.
constexpr uint8_t kLowestPriority = 255;
uint8_t getTablePriority(uint16_t platformId, uint16_t encodingId) {
if (platformId == 3 && encodingId == 10) {
return 0;
}
if (platformId == 0 && encodingId == 6) {
return 1;
}
if (platformId == 0 && encodingId == 4) {
return 2;
}
if (platformId == 3 && encodingId == 1) {
return 3;
}
if (platformId == 0 && encodingId == 3) {
return 4;
}
if (platformId == 0 && encodingId == 2) {
return 5;
}
if (platformId == 0 && encodingId == 1) {
return 6;
}
if (platformId == 0 && encodingId == 0) {
return 7;
}
// Tables other than above are not supported.
return kLowestPriority;
}
SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_size,
bool* has_cmap_format14_subtable) {
constexpr size_t kHeaderSize = 4;
constexpr size_t kNumTablesOffset = 2;
constexpr size_t kTableSize = 8;
constexpr size_t kPlatformIdOffset = 0;
constexpr size_t kEncodingIdOffset = 2;
constexpr size_t kOffsetOffset = 4;
constexpr size_t kFormatOffset = 0;
constexpr uint32_t kInvalidOffset = UINT32_MAX;
if (kHeaderSize > cmap_size) {
return SparseBitSet();
}
uint32_t numTables = readU16(cmap_data, kNumTablesOffset);
if (kHeaderSize + numTables * kTableSize > cmap_size) {
return SparseBitSet();
}
uint32_t bestTableOffset = kInvalidOffset;
uint16_t bestTableFormat = 0;
uint8_t bestTablePriority = kLowestPriority;
*has_cmap_format14_subtable = false;
for (uint32_t i = 0; i < numTables; ++i) {
const uint32_t tableHeadOffset = kHeaderSize + i * kTableSize;
const uint16_t platformId = readU16(cmap_data, tableHeadOffset + kPlatformIdOffset);
const uint16_t encodingId = readU16(cmap_data, tableHeadOffset + kEncodingIdOffset);
const uint32_t offset = readU32(cmap_data, tableHeadOffset + kOffsetOffset);
if (offset > cmap_size - 2) {
continue; // Invalid table: not enough space to read.
}
const uint16_t format = readU16(cmap_data, offset + kFormatOffset);
if (platformId == 0 /* Unicode */ && encodingId == 5 /* Variation Sequences */) {
if (!(*has_cmap_format14_subtable) && format == 14) {
*has_cmap_format14_subtable = true;
} else {
// Ignore the (0, 5) table if we have already seen another valid one or it's in a
// format we don't understand.
}
} else {
uint32_t length;
uint32_t language;
if (format == 4) {
constexpr size_t lengthOffset = 2;
constexpr size_t languageOffset = 4;
constexpr size_t minTableSize = languageOffset + 2;
if (offset > cmap_size - minTableSize) {
continue; // Invalid table: not enough space to read.
}
length = readU16(cmap_data, offset + lengthOffset);
language = readU16(cmap_data, offset + languageOffset);
} else if (format == 12) {
constexpr size_t lengthOffset = 4;
constexpr size_t languageOffset = 8;
constexpr size_t minTableSize = languageOffset + 4;
if (offset > cmap_size - minTableSize) {
continue; // Invalid table: not enough space to read.
}
length = readU32(cmap_data, offset + lengthOffset);
language = readU32(cmap_data, offset + languageOffset);
} else {
continue;
}
if (length > cmap_size - offset) {
continue; // Invalid table: table length is larger than whole cmap data size.
}
if (language != 0) {
// Unsupported or invalid table: this is either a subtable for the Macintosh
// platform (which we don't support), or an invalid subtable since language field
// should be zero for non-Macintosh subtables.
continue;
}
const uint8_t priority = getTablePriority(platformId, encodingId);
if (priority < bestTablePriority) {
bestTableOffset = offset;
bestTablePriority = priority;
bestTableFormat = format;
}
}
if (*has_cmap_format14_subtable && bestTablePriority == 0 /* highest priority */) {
// Already found the highest priority table and variation sequences table. No need to
// look at remaining tables.
break;
}
}
if (bestTableOffset == kInvalidOffset) {
return SparseBitSet();
}
const uint8_t* tableData = cmap_data + bestTableOffset;
const size_t tableSize = cmap_size - bestTableOffset;
vector<uint32_t> coverageVec;
bool success;
if (bestTableFormat == 4) {
success = getCoverageFormat4(coverageVec, tableData, tableSize);
} else {
success = getCoverageFormat12(coverageVec, tableData, tableSize);
}
if (success) {
return SparseBitSet(&coverageVec.front(), coverageVec.size() >> 1);
} else {
return SparseBitSet();
}
}
} // namespace minikin

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.
*/
#include <minikin/Emoji.h>
namespace minikin {
bool isNewEmoji(uint32_t c) {
// Emoji characters new in Unicode emoji 5.0.
// From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
// TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
if (c < 0x1F6F7 || c > 0x1F9E6) {
// Optimization for characters outside the new emoji range.
return false;
}
return (0x1F6F7 <= c && c <= 0x1F6F8)
|| c == 0x1F91F
|| (0x1F928 <= c && c <= 0x1F92F)
|| (0x1F931 <= c && c <= 0x1F932)
|| c == 0x1F94C
|| (0x1F95F <= c && c <= 0x1F96B)
|| (0x1F992 <= c && c <= 0x1F997)
|| (0x1F9D0 <= c && c <= 0x1F9E6);
}
bool isEmoji(uint32_t c) {
#if WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
return isNewEmoji(c) || u_hasBinaryProperty(c, UCHAR_EMOJI);
#endif // WIP_NEEDS_ICU_UPDATE
}
bool isEmojiModifier(uint32_t c) {
#if WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
// Emoji modifier are not expected to change, so there's a small change we need to customize
// this.
return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER);
#endif // WIP_NEEDS_ICU_UPDATE
}
bool isEmojiBase(uint32_t c) {
#if WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
// These two characters were removed from Emoji_Modifier_Base in Emoji 4.0, but we need to keep
// them as emoji modifier bases since there are fonts and user-generated text out there that
// treats these as potential emoji bases.
if (c == 0x1F91D || c == 0x1F93C) {
return true;
}
// Emoji Modifier Base characters new in Unicode emoji 5.0.
// From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
// TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
if (c == 0x1F91F
|| (0x1F931 <= c && c <= 0x1F932)
|| (0x1F9D1 <= c && c <= 0x1F9DD)) {
return true;
}
return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER_BASE);
#endif // WIP_NEEDS_ICU_UPDATE
}
UCharDirection emojiBidiOverride(const void* /* context */, UChar32 c) {
if (isNewEmoji(c)) {
// All new emoji characters in Unicode 10.0 are of the bidi class ON.
return U_OTHER_NEUTRAL;
} else {
return u_charDirection(c);
}
}
} // namespace minikin

View File

@@ -0,0 +1,471 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.
*/
// #define VERBOSE_DEBUG
#define LOG_TAG "Minikin"
#include <algorithm>
#include <log/log.h>
#include "unicode/unistr.h"
#include "unicode/unorm2.h"
#include "FontLanguage.h"
#include "FontLanguageListCache.h"
#include "MinikinInternal.h"
#include <minikin/Emoji.h>
#include <minikin/FontCollection.h>
using std::vector;
namespace minikin {
template <typename T>
static inline T max(T a, T b) {
return a>b ? a : b;
}
const uint32_t EMOJI_STYLE_VS = 0xFE0F;
const uint32_t TEXT_STYLE_VS = 0xFE0E;
uint32_t FontCollection::sNextId = 0;
FontCollection::FontCollection(std::shared_ptr<FontFamily>&& typeface) : mMaxChar(0) {
std::vector<std::shared_ptr<FontFamily>> typefaces;
typefaces.push_back(typeface);
init(typefaces);
}
FontCollection::FontCollection(const vector<std::shared_ptr<FontFamily>>& typefaces) :
mMaxChar(0) {
init(typefaces);
}
void FontCollection::init(const vector<std::shared_ptr<FontFamily>>& typefaces) {
std::lock_guard<std::mutex> _l(gMinikinLock);
mId = sNextId++;
vector<uint32_t> lastChar;
size_t nTypefaces = typefaces.size();
#ifdef VERBOSE_DEBUG
ALOGD("nTypefaces = %zd\n", nTypefaces);
#endif
const FontStyle defaultStyle;
for (size_t i = 0; i < nTypefaces; i++) {
const std::shared_ptr<FontFamily>& family = typefaces[i];
if (family->getClosestMatch(defaultStyle).font == nullptr) {
continue;
}
const SparseBitSet& coverage = family->getCoverage();
mFamilies.push_back(family); // emplace_back would be better
if (family->hasVSTable()) {
mVSFamilyVec.push_back(family);
}
mMaxChar = max(mMaxChar, coverage.length());
lastChar.push_back(coverage.nextSetBit(0));
const std::unordered_set<AxisTag>& supportedAxes = family->supportedAxes();
mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());
}
nTypefaces = mFamilies.size();
LOG_ALWAYS_FATAL_IF(nTypefaces == 0,
"Font collection must have at least one valid typeface");
LOG_ALWAYS_FATAL_IF(nTypefaces > 254,
"Font collection may only have up to 254 font families.");
size_t nPages = (mMaxChar + kPageMask) >> kLogCharsPerPage;
// TODO: Use variation selector map for mRanges construction.
// A font can have a glyph for a base code point and variation selector pair but no glyph for
// the base code point without variation selector. The family won't be listed in the range in
// this case.
for (size_t i = 0; i < nPages; i++) {
Range dummy;
mRanges.push_back(dummy);
Range* range = &mRanges.back();
#ifdef VERBOSE_DEBUG
ALOGD("i=%zd: range start = %zd\n", i, offset);
#endif
range->start = mFamilyVec.size();
for (size_t j = 0; j < nTypefaces; j++) {
if (lastChar[j] < (i + 1) << kLogCharsPerPage) {
const std::shared_ptr<FontFamily>& family = mFamilies[j];
mFamilyVec.push_back(static_cast<uint8_t>(j));
uint32_t nextChar = family->getCoverage().nextSetBit((i + 1) << kLogCharsPerPage);
#ifdef VERBOSE_DEBUG
ALOGD("nextChar = %d (j = %zd)\n", nextChar, j);
#endif
lastChar[j] = nextChar;
}
}
range->end = mFamilyVec.size();
}
// See the comment in Range for more details.
LOG_ALWAYS_FATAL_IF(mFamilyVec.size() >= 0xFFFF,
"Exceeded the maximum indexable cmap coverage.");
}
// Special scores for the font fallback.
const uint32_t kUnsupportedFontScore = 0;
const uint32_t kFirstFontScore = UINT32_MAX;
// Calculates a font score.
// The score of the font family is based on three subscores.
// - Coverage Score: How well the font family covers the given character or variation sequence.
// - Language Score: How well the font family is appropriate for the language.
// - Variant Score: Whether the font family matches the variant. Note that this variant is not the
// one in BCP47. This is our own font variant (e.g., elegant, compact).
//
// Then, there is a priority for these three subscores as follow:
// Coverage Score > Language Score > Variant Score
// The returned score reflects this priority order.
//
// Note that there are two special scores.
// - kUnsupportedFontScore: When the font family doesn't support the variation sequence or even its
// base character.
// - kFirstFontScore: When the font is the first font family in the collection and it supports the
// given character or variation sequence.
uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, int variant, uint32_t langListId,
const std::shared_ptr<FontFamily>& fontFamily) const {
const uint32_t coverageScore = calcCoverageScore(ch, vs, fontFamily);
if (coverageScore == kFirstFontScore || coverageScore == kUnsupportedFontScore) {
// No need to calculate other scores.
return coverageScore;
}
const uint32_t languageScore = calcLanguageMatchingScore(langListId, *fontFamily);
const uint32_t variantScore = calcVariantMatchingScore(variant, *fontFamily);
// Subscores are encoded into 31 bits representation to meet the subscore priority.
// The highest 2 bits are for coverage score, then following 28 bits are for language score,
// then the last 1 bit is for variant score.
return coverageScore << 29 | languageScore << 1 | variantScore;
}
// Calculates a font score based on variation sequence coverage.
// - Returns kUnsupportedFontScore if the font doesn't support the variation sequence or its base
// character.
// - Returns kFirstFontScore if the font family is the first font family in the collection and it
// supports the given character or variation sequence.
// - Returns 3 if the font family supports the variation sequence.
// - Returns 2 if the vs is a color variation selector (U+FE0F) and if the font is an emoji font.
// - Returns 2 if the vs is a text variation selector (U+FE0E) and if the font is not an emoji font.
// - Returns 1 if the variation selector is not specified or if the font family only supports the
// variation sequence's base character.
uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs,
const std::shared_ptr<FontFamily>& fontFamily) const {
const bool hasVSGlyph = (vs != 0) && fontFamily->hasGlyph(ch, vs);
if (!hasVSGlyph && !fontFamily->getCoverage().get(ch)) {
// The font doesn't support either variation sequence or even the base character.
return kUnsupportedFontScore;
}
if ((vs == 0 || hasVSGlyph) && mFamilies[0] == fontFamily) {
// If the first font family supports the given character or variation sequence, always use
// it.
return kFirstFontScore;
}
if (vs == 0) {
return 1;
}
if (hasVSGlyph) {
return 3;
}
if (vs == EMOJI_STYLE_VS || vs == TEXT_STYLE_VS) {
const FontLanguages& langs = FontLanguageListCache::getById(fontFamily->langId());
bool hasEmojiFlag = false;
for (size_t i = 0; i < langs.size(); ++i) {
if (langs[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {
hasEmojiFlag = true;
break;
}
}
if (vs == EMOJI_STYLE_VS) {
return hasEmojiFlag ? 2 : 1;
} else { // vs == TEXT_STYLE_VS
return hasEmojiFlag ? 1 : 2;
}
}
return 1;
}
// Calculate font scores based on the script matching, subtag matching and primary langauge matching.
//
// 1. If only the font's language matches or there is no matches between requested font and
// supported font, then the font obtains a score of 0.
// 2. Without a match in language, considering subtag may change font's EmojiStyle over script,
// a match in subtag gets a score of 2 and a match in scripts gains a score of 1.
// 3. Regarding to two elements matchings, language-and-subtag matching has a score of 4, while
// language-and-script obtains a socre of 3 with the same reason above.
//
// If two languages in the requested list have the same language score, the font matching with
// higher priority language gets a higher score. For example, in the case the user requested
// language list is "ja-Jpan,en-Latn". The score of for the font of "ja-Jpan" gets a higher score
// than the font of "en-Latn".
//
// To achieve score calculation with priorities, the language score is determined as follows:
// LanguageScore = s(0) * 5^(m - 1) + s(1) * 5^(m - 2) + ... + s(m - 2) * 5 + s(m - 1)
// Here, m is the maximum number of languages to be compared, and s(i) is the i-th language's
// matching score. The possible values of s(i) are 0, 1, 2, 3 and 4.
uint32_t FontCollection::calcLanguageMatchingScore(
uint32_t userLangListId, const FontFamily& fontFamily) {
const FontLanguages& langList = FontLanguageListCache::getById(userLangListId);
const FontLanguages& fontLanguages = FontLanguageListCache::getById(fontFamily.langId());
const size_t maxCompareNum = std::min(langList.size(), FONT_LANGUAGES_LIMIT);
uint32_t score = 0;
for (size_t i = 0; i < maxCompareNum; ++i) {
score = score * 5u + langList[i].calcScoreFor(fontLanguages);
}
return score;
}
// Calculates a font score based on variant ("compact" or "elegant") matching.
// - Returns 1 if the font doesn't have variant or the variant matches with the text style.
// - No score if the font has a variant but it doesn't match with the text style.
uint32_t FontCollection::calcVariantMatchingScore(int variant, const FontFamily& fontFamily) {
return (fontFamily.variant() == 0 || fontFamily.variant() == variant) ? 1 : 0;
}
// Implement heuristic for choosing best-match font. Here are the rules:
// 1. If first font in the collection has the character, it wins.
// 2. Calculate a score for the font family. See comments in calcFamilyScore for the detail.
// 3. Highest score wins, with ties resolved to the first font.
// This method never returns nullptr.
const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs,
uint32_t langListId, int variant) const {
if (ch >= mMaxChar) {
return mFamilies[0];
}
Range range = mRanges[ch >> kLogCharsPerPage];
if (vs != 0) {
range = { 0, static_cast<uint16_t>(mFamilies.size()) };
}
#ifdef VERBOSE_DEBUG
ALOGD("querying range %zd:%zd\n", range.start, range.end);
#endif
int bestFamilyIndex = -1;
uint32_t bestScore = kUnsupportedFontScore;
for (size_t i = range.start; i < range.end; i++) {
const std::shared_ptr<FontFamily>& family =
vs == 0 ? mFamilies[mFamilyVec[i]] : mFamilies[i];
const uint32_t score = calcFamilyScore(ch, vs, variant, langListId, family);
if (score == kFirstFontScore) {
// If the first font family supports the given character or variation sequence, always
// use it.
return family;
}
if (score > bestScore) {
bestScore = score;
bestFamilyIndex = i;
}
}
if (bestFamilyIndex == -1) {
UErrorCode errorCode = U_ZERO_ERROR;
const UNormalizer2* normalizer = unorm2_getNFDInstance(&errorCode);
if (U_SUCCESS(errorCode)) {
UChar decomposed[4];
int len = unorm2_getRawDecomposition(normalizer, ch, decomposed, 4, &errorCode);
if (U_SUCCESS(errorCode) && len > 0) {
int off = 0;
U16_NEXT_UNSAFE(decomposed, off, ch);
return getFamilyForChar(ch, vs, langListId, variant);
}
}
return mFamilies[0];
}
return vs == 0 ? mFamilies[mFamilyVec[bestFamilyIndex]] : mFamilies[bestFamilyIndex];
}
const uint32_t NBSP = 0x00A0;
const uint32_t SOFT_HYPHEN = 0x00AD;
const uint32_t ZWJ = 0x200C;
const uint32_t ZWNJ = 0x200D;
const uint32_t HYPHEN = 0x2010;
const uint32_t NB_HYPHEN = 0x2011;
const uint32_t NNBSP = 0x202F;
const uint32_t FEMALE_SIGN = 0x2640;
const uint32_t MALE_SIGN = 0x2642;
const uint32_t STAFF_OF_AESCULAPIUS = 0x2695;
// Characters where we want to continue using existing font run instead of
// recomputing the best match in the fallback list.
static const uint32_t stickyWhitelist[] = {
'!', ',', '-', '.', ':', ';', '?', NBSP, ZWJ, ZWNJ,
HYPHEN, NB_HYPHEN, NNBSP, FEMALE_SIGN, MALE_SIGN, STAFF_OF_AESCULAPIUS };
static bool isStickyWhitelisted(uint32_t c) {
for (size_t i = 0; i < sizeof(stickyWhitelist) / sizeof(stickyWhitelist[0]); i++) {
if (stickyWhitelist[i] == c) return true;
}
return false;
}
static bool isVariationSelector(uint32_t c) {
return (0xFE00 <= c && c <= 0xFE0F) || (0xE0100 <= c && c <= 0xE01EF);
}
bool FontCollection::hasVariationSelector(uint32_t baseCodepoint,
uint32_t variationSelector) const {
if (!isVariationSelector(variationSelector)) {
return false;
}
if (baseCodepoint >= mMaxChar) {
return false;
}
std::lock_guard<std::mutex> _l(gMinikinLock);
// Currently mRanges can not be used here since it isn't aware of the variation sequence.
for (size_t i = 0; i < mVSFamilyVec.size(); i++) {
if (mVSFamilyVec[i]->hasGlyph(baseCodepoint, variationSelector)) {
return true;
}
}
// Even if there is no cmap format 14 subtable entry for the given sequence, should return true
// for <char, text presentation selector> case since we have special fallback rule for the
// sequence. Note that we don't need to restrict this to already standardized variation
// sequences, since Unicode is adding variation sequences more frequently now and may even move
// towards allowing text and emoji variation selectors on any character.
if (variationSelector == TEXT_STYLE_VS) {
for (size_t i = 0; i < mFamilies.size(); ++i) {
if (!mFamilies[i]->isColorEmojiFamily() && mFamilies[i]->hasGlyph(baseCodepoint, 0)) {
return true;
}
}
}
return false;
}
void FontCollection::itemize(const uint16_t *string, size_t string_size, FontStyle style,
vector<Run>* result) const {
const uint32_t langListId = style.getLanguageListId();
int variant = style.getVariant();
const FontFamily* lastFamily = nullptr;
Run* run = NULL;
if (string_size == 0) {
return;
}
const uint32_t kEndOfString = 0xFFFFFFFF;
uint32_t nextCh = 0;
uint32_t prevCh = 0;
size_t nextUtf16Pos = 0;
size_t readLength = 0;
U16_NEXT(string, readLength, string_size, nextCh);
do {
const uint32_t ch = nextCh;
const size_t utf16Pos = nextUtf16Pos;
nextUtf16Pos = readLength;
if (readLength < string_size) {
U16_NEXT(string, readLength, string_size, nextCh);
} else {
nextCh = kEndOfString;
}
bool shouldContinueRun = false;
if (lastFamily != nullptr) {
if (isStickyWhitelisted(ch)) {
// Continue using existing font as long as it has coverage and is whitelisted
shouldContinueRun = lastFamily->getCoverage().get(ch);
} else if (ch == SOFT_HYPHEN || isVariationSelector(ch)) {
// Always continue if the character is the soft hyphen or a variation selector.
shouldContinueRun = true;
}
}
if (!shouldContinueRun) {
const std::shared_ptr<FontFamily>& family = getFamilyForChar(
ch, isVariationSelector(nextCh) ? nextCh : 0, langListId, variant);
if (utf16Pos == 0 || family.get() != lastFamily) {
size_t start = utf16Pos;
// Workaround for combining marks and emoji modifiers until we implement
// per-cluster font selection: if a combining mark or an emoji modifier is found in
// a different font that also supports the previous character, attach previous
// character to the new run. U+20E3 COMBINING ENCLOSING KEYCAP, used in emoji, is
// handled properly by this since it's a combining mark too.
if (utf16Pos != 0 &&
((U_GET_GC_MASK(ch) & U_GC_M_MASK) != 0 ||
(isEmojiModifier(ch) && isEmojiBase(prevCh))) &&
family != nullptr && family->getCoverage().get(prevCh)) {
const size_t prevChLength = U16_LENGTH(prevCh);
run->end -= prevChLength;
if (run->start == run->end) {
result->pop_back();
}
start -= prevChLength;
}
result->push_back({family->getClosestMatch(style), static_cast<int>(start), 0});
run = &result->back();
lastFamily = family.get();
}
}
prevCh = ch;
run->end = nextUtf16Pos; // exclusive
} while (nextCh != kEndOfString);
}
FakedFont FontCollection::baseFontFaked(FontStyle style) {
return mFamilies[0]->getClosestMatch(style);
}
std::shared_ptr<FontCollection> FontCollection::createCollectionWithVariation(
const std::vector<FontVariation>& variations) {
if (variations.empty() || mSupportedAxes.empty()) {
return nullptr;
}
bool hasSupportedAxis = false;
for (const FontVariation& variation : variations) {
if (mSupportedAxes.find(variation.axisTag) != mSupportedAxes.end()) {
hasSupportedAxis = true;
break;
}
}
if (!hasSupportedAxis) {
// None of variation axes are supported by this font collection.
return nullptr;
}
std::vector<std::shared_ptr<FontFamily> > families;
for (const std::shared_ptr<FontFamily>& family : mFamilies) {
std::shared_ptr<FontFamily> newFamily = family->createFamilyWithVariation(variations);
if (newFamily) {
families.push_back(newFamily);
} else {
families.push_back(family);
}
}
return std::shared_ptr<FontCollection>(new FontCollection(families));
}
uint32_t FontCollection::getId() const {
return mId;
}
} // namespace minikin

View File

@@ -0,0 +1,246 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "Minikin"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <log/log.h>
#include <utils/JenkinsHash.h>
#include <hb.h>
#include <hb-ot.h>
#include "FontLanguage.h"
#include "FontLanguageListCache.h"
#include "FontUtils.h"
#include "HbFontCache.h"
#include "MinikinInternal.h"
#include <minikin/CmapCoverage.h>
#include <minikin/MinikinFont.h>
#include <minikin/FontFamily.h>
#include <minikin/MinikinFont.h>
using std::vector;
namespace minikin {
FontStyle::FontStyle(int variant, int weight, bool italic)
: FontStyle(FontLanguageListCache::kEmptyListId, variant, weight, italic) {
}
FontStyle::FontStyle(uint32_t languageListId, int variant, int weight, bool italic)
: bits(pack(variant, weight, italic)), mLanguageListId(languageListId) {
}
android::hash_t FontStyle::hash() const {
uint32_t hash = android::JenkinsHashMix(0, bits);
hash = android::JenkinsHashMix(hash, mLanguageListId);
return android::JenkinsHashWhiten(hash);
}
// static
uint32_t FontStyle::registerLanguageList(const std::string& languages) {
std::lock_guard<std::mutex> _l(gMinikinLock);
return FontLanguageListCache::getId(languages);
}
// static
uint32_t FontStyle::pack(int variant, int weight, bool italic) {
return (weight & kWeightMask) | (italic ? kItalicMask : 0) | (variant << kVariantShift);
}
Font::Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style)
: typeface(typeface), style(style) {
}
Font::Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style)
: typeface(typeface), style(style) {
}
std::unordered_set<AxisTag> Font::getSupportedAxesLocked() const {
const uint32_t fvarTag = MinikinFont::MakeTag('f', 'v', 'a', 'r');
HbBlob fvarTable(getFontTable(typeface.get(), fvarTag));
if (fvarTable.size() == 0) {
return std::unordered_set<AxisTag>();
}
std::unordered_set<AxisTag> supportedAxes;
analyzeAxes(fvarTable.get(), fvarTable.size(), &supportedAxes);
return supportedAxes;
}
Font::Font(Font&& o) {
typeface = std::move(o.typeface);
style = o.style;
o.typeface = nullptr;
}
Font::Font(const Font& o) {
typeface = o.typeface;
style = o.style;
}
// static
FontFamily::FontFamily(std::vector<Font>&& fonts) : FontFamily(0 /* variant */, std::move(fonts)) {
}
FontFamily::FontFamily(int variant, std::vector<Font>&& fonts)
: FontFamily(FontLanguageListCache::kEmptyListId, variant, std::move(fonts)) {
}
FontFamily::FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts)
: mLangId(langId), mVariant(variant), mFonts(std::move(fonts)), mHasVSTable(false) {
computeCoverage();
}
bool FontFamily::analyzeStyle(const std::shared_ptr<MinikinFont>& typeface, int* weight,
bool* italic) {
std::lock_guard<std::mutex> _l(gMinikinLock);
const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');
HbBlob os2Table(getFontTable(typeface.get(), os2Tag));
if (os2Table.get() == nullptr) return false;
return ::minikin::analyzeStyle(os2Table.get(), os2Table.size(), weight, italic);
}
// Compute a matching metric between two styles - 0 is an exact match
static int computeMatch(FontStyle style1, FontStyle style2) {
if (style1 == style2) return 0;
int score = abs(style1.getWeight() - style2.getWeight());
if (style1.getItalic() != style2.getItalic()) {
score += 2;
}
return score;
}
static FontFakery computeFakery(FontStyle wanted, FontStyle actual) {
// If desired weight is semibold or darker, and 2 or more grades
// higher than actual (for example, medium 500 -> bold 700), then
// select fake bold.
int wantedWeight = wanted.getWeight();
bool isFakeBold = wantedWeight >= 6 && (wantedWeight - actual.getWeight()) >= 2;
bool isFakeItalic = wanted.getItalic() && !actual.getItalic();
return FontFakery(isFakeBold, isFakeItalic);
}
FakedFont FontFamily::getClosestMatch(FontStyle style) const {
const Font* bestFont = nullptr;
int bestMatch = 0;
for (size_t i = 0; i < mFonts.size(); i++) {
const Font& font = mFonts[i];
int match = computeMatch(font.style, style);
if (i == 0 || match < bestMatch) {
bestFont = &font;
bestMatch = match;
}
}
if (bestFont != nullptr) {
return FakedFont{ bestFont->typeface.get(), computeFakery(style, bestFont->style) };
}
return FakedFont{ nullptr, FontFakery() };
}
bool FontFamily::isColorEmojiFamily() const {
const FontLanguages& languageList = FontLanguageListCache::getById(mLangId);
for (size_t i = 0; i < languageList.size(); ++i) {
if (languageList[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {
return true;
}
}
return false;
}
void FontFamily::computeCoverage() {
std::lock_guard<std::mutex> _l(gMinikinLock);
const FontStyle defaultStyle;
const MinikinFont* typeface = getClosestMatch(defaultStyle).font;
const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
HbBlob cmapTable(getFontTable(typeface, cmapTag));
if (cmapTable.get() == nullptr) {
ALOGE("Could not get cmap table size!\n");
return;
}
mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mHasVSTable);
for (size_t i = 0; i < mFonts.size(); ++i) {
std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxesLocked();
mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());
}
}
bool FontFamily::hasGlyph(uint32_t codepoint, uint32_t variationSelector) const {
assertMinikinLocked();
if (variationSelector != 0 && !mHasVSTable) {
// Early exit if the variation selector is specified but the font doesn't have a cmap format
// 14 subtable.
return false;
}
const FontStyle defaultStyle;
hb_font_t* font = getHbFontLocked(getClosestMatch(defaultStyle).font);
uint32_t unusedGlyph;
bool result = hb_font_get_glyph(font, codepoint, variationSelector, &unusedGlyph);
hb_font_destroy(font);
return result;
}
std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation(
const std::vector<FontVariation>& variations) const {
if (variations.empty() || mSupportedAxes.empty()) {
return nullptr;
}
bool hasSupportedAxis = false;
for (const FontVariation& variation : variations) {
if (mSupportedAxes.find(variation.axisTag) != mSupportedAxes.end()) {
hasSupportedAxis = true;
break;
}
}
if (!hasSupportedAxis) {
// None of variation axes are suppored by this family.
return nullptr;
}
std::vector<Font> fonts;
for (const Font& font : mFonts) {
bool supportedVariations = false;
std::lock_guard<std::mutex> _l(gMinikinLock);
std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxesLocked();
if (!supportedAxes.empty()) {
for (const FontVariation& variation : variations) {
if (supportedAxes.find(variation.axisTag) != supportedAxes.end()) {
supportedVariations = true;
break;
}
}
}
std::shared_ptr<MinikinFont> minikinFont;
if (supportedVariations) {
minikinFont = font.typeface->createFontWithVariation(variations);
}
if (minikinFont == nullptr) {
minikinFont = font.typeface;
}
fonts.push_back(Font(std::move(minikinFont), font.style));
}
return std::shared_ptr<FontFamily>(new FontFamily(mLangId, mVariant, std::move(fonts)));
}
} // namespace minikin

View File

@@ -0,0 +1,345 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "Minikin"
#include "FontLanguage.h"
#include <algorithm>
#include <hb.h>
#include <string.h>
#include <unicode/uloc.h>
namespace minikin {
#define SCRIPT_TAG(c1, c2, c3, c4) \
(((uint32_t)(c1)) << 24 | ((uint32_t)(c2)) << 16 | ((uint32_t)(c3)) << 8 | \
((uint32_t)(c4)))
// Check if a language code supports emoji according to its subtag
static bool isEmojiSubtag(const char* buf, size_t bufLen, const char* subtag, size_t subtagLen) {
if (bufLen < subtagLen) {
return false;
}
if (strncmp(buf, subtag, subtagLen) != 0) {
return false; // no match between two strings
}
return (bufLen == subtagLen || buf[subtagLen] == '\0' ||
buf[subtagLen] == '-' || buf[subtagLen] == '_');
}
// Pack the three letter code into 15 bits and stored to 16 bit integer. The highest bit is 0.
// For the region code, the letters must be all digits in three letter case, so the number of
// possible values are 10. For the language code, the letters must be all small alphabets, so the
// number of possible values are 26. Thus, 5 bits are sufficient for each case and we can pack the
// three letter language code or region code to 15 bits.
//
// In case of two letter code, use fullbit(0x1f) for the first letter instead.
static uint16_t packLanguageOrRegion(const char* c, size_t length, uint8_t twoLetterBase,
uint8_t threeLetterBase) {
if (length == 2) {
return 0x7c00u | // 0x1fu << 10
(uint16_t)(c[0] - twoLetterBase) << 5 |
(uint16_t)(c[1] - twoLetterBase);
} else {
return ((uint16_t)(c[0] - threeLetterBase) << 10) |
(uint16_t)(c[1] - threeLetterBase) << 5 |
(uint16_t)(c[2] - threeLetterBase);
}
}
static size_t unpackLanguageOrRegion(uint16_t in, char* out, uint8_t twoLetterBase,
uint8_t threeLetterBase) {
uint8_t first = (in >> 10) & 0x1f;
uint8_t second = (in >> 5) & 0x1f;
uint8_t third = in & 0x1f;
if (first == 0x1f) {
out[0] = second + twoLetterBase;
out[1] = third + twoLetterBase;
return 2;
} else {
out[0] = first + threeLetterBase;
out[1] = second + threeLetterBase;
out[2] = third + threeLetterBase;
return 3;
}
}
// Find the next '-' or '_' index from startOffset position. If not found, returns bufferLength.
static size_t nextDelimiterIndex(const char* buffer, size_t bufferLength, size_t startOffset) {
for (size_t i = startOffset; i < bufferLength; ++i) {
if (buffer[i] == '-' || buffer[i] == '_') {
return i;
}
}
return bufferLength;
}
static inline bool isLowercase(char c) {
return 'a' <= c && c <= 'z';
}
static inline bool isUppercase(char c) {
return 'A' <= c && c <= 'Z';
}
static inline bool isDigit(char c) {
return '0' <= c && c <= '9';
}
// Returns true if the buffer is valid for language code.
static inline bool isValidLanguageCode(const char* buffer, size_t length) {
if (length != 2 && length != 3) return false;
if (!isLowercase(buffer[0])) return false;
if (!isLowercase(buffer[1])) return false;
if (length == 3 && !isLowercase(buffer[2])) return false;
return true;
}
// Returns true if buffer is valid for script code. The length of buffer must be 4.
static inline bool isValidScriptCode(const char* buffer) {
return isUppercase(buffer[0]) && isLowercase(buffer[1]) && isLowercase(buffer[2]) &&
isLowercase(buffer[3]);
}
// Returns true if the buffer is valid for region code.
static inline bool isValidRegionCode(const char* buffer, size_t length) {
return (length == 2 && isUppercase(buffer[0]) && isUppercase(buffer[1])) ||
(length == 3 && isDigit(buffer[0]) && isDigit(buffer[1]) && isDigit(buffer[2]));
}
// Parse BCP 47 language identifier into internal structure
FontLanguage::FontLanguage(const char* buf, size_t length) : FontLanguage() {
size_t firstDelimiterPos = nextDelimiterIndex(buf, length, 0);
if (isValidLanguageCode(buf, firstDelimiterPos)) {
mLanguage = packLanguageOrRegion(buf, firstDelimiterPos, 'a', 'a');
} else {
// We don't understand anything other than two-letter or three-letter
// language codes, so we skip parsing the rest of the string.
return;
}
if (firstDelimiterPos == length) {
mHbLanguage = hb_language_from_string(getString().c_str(), -1);
return; // Language code only.
}
size_t nextComponentStartPos = firstDelimiterPos + 1;
size_t nextDelimiterPos = nextDelimiterIndex(buf, length, nextComponentStartPos);
size_t componentLength = nextDelimiterPos - nextComponentStartPos;
if (componentLength == 4) {
// Possibly script code.
const char* p = buf + nextComponentStartPos;
if (isValidScriptCode(p)) {
mScript = SCRIPT_TAG(p[0], p[1], p[2], p[3]);
mSubScriptBits = scriptToSubScriptBits(mScript);
}
if (nextDelimiterPos == length) {
mHbLanguage = hb_language_from_string(getString().c_str(), -1);
mEmojiStyle = resolveEmojiStyle(buf, length, mScript);
return; // No region code.
}
nextComponentStartPos = nextDelimiterPos + 1;
nextDelimiterPos = nextDelimiterIndex(buf, length, nextComponentStartPos);
componentLength = nextDelimiterPos - nextComponentStartPos;
}
if (componentLength == 2 || componentLength == 3) {
// Possibly region code.
const char* p = buf + nextComponentStartPos;
if (isValidRegionCode(p, componentLength)) {
mRegion = packLanguageOrRegion(p, componentLength, 'A', '0');
}
}
mHbLanguage = hb_language_from_string(getString().c_str(), -1);
mEmojiStyle = resolveEmojiStyle(buf, length, mScript);
}
// static
FontLanguage::EmojiStyle FontLanguage::resolveEmojiStyle(const char* buf, size_t length,
uint32_t script) {
// First, lookup emoji subtag.
// 10 is the length of "-u-em-text", which is the shortest emoji subtag,
// unnecessary comparison can be avoided if total length is smaller than 10.
const size_t kMinSubtagLength = 10;
if (length >= kMinSubtagLength) {
static const char kPrefix[] = "-u-em-";
const char *pos = std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));
if (pos != buf + length) { // found
pos += strlen(kPrefix);
const size_t remainingLength = length - (pos - buf);
if (isEmojiSubtag(pos, remainingLength, "emoji", 5)){
return EMSTYLE_EMOJI;
} else if (isEmojiSubtag(pos, remainingLength, "text", 4)){
return EMSTYLE_TEXT;
} else if (isEmojiSubtag(pos, remainingLength, "default", 7)){
return EMSTYLE_DEFAULT;
}
}
}
// If no emoji subtag was provided, resolve the emoji style from script code.
if (script == SCRIPT_TAG('Z', 's', 'y', 'e')) {
return EMSTYLE_EMOJI;
} else if (script == SCRIPT_TAG('Z', 's', 'y', 'm')) {
return EMSTYLE_TEXT;
}
return EMSTYLE_EMPTY;
}
//static
uint8_t FontLanguage::scriptToSubScriptBits(uint32_t script) {
uint8_t subScriptBits = 0u;
switch (script) {
case SCRIPT_TAG('B', 'o', 'p', 'o'):
subScriptBits = kBopomofoFlag;
break;
case SCRIPT_TAG('H', 'a', 'n', 'g'):
subScriptBits = kHangulFlag;
break;
case SCRIPT_TAG('H', 'a', 'n', 'b'):
// Bopomofo is almost exclusively used in Taiwan.
subScriptBits = kHanFlag | kBopomofoFlag;
break;
case SCRIPT_TAG('H', 'a', 'n', 'i'):
subScriptBits = kHanFlag;
break;
case SCRIPT_TAG('H', 'a', 'n', 's'):
subScriptBits = kHanFlag | kSimplifiedChineseFlag;
break;
case SCRIPT_TAG('H', 'a', 'n', 't'):
subScriptBits = kHanFlag | kTraditionalChineseFlag;
break;
case SCRIPT_TAG('H', 'i', 'r', 'a'):
subScriptBits = kHiraganaFlag;
break;
case SCRIPT_TAG('H', 'r', 'k', 't'):
subScriptBits = kKatakanaFlag | kHiraganaFlag;
break;
case SCRIPT_TAG('J', 'p', 'a', 'n'):
subScriptBits = kHanFlag | kKatakanaFlag | kHiraganaFlag;
break;
case SCRIPT_TAG('K', 'a', 'n', 'a'):
subScriptBits = kKatakanaFlag;
break;
case SCRIPT_TAG('K', 'o', 'r', 'e'):
subScriptBits = kHanFlag | kHangulFlag;
break;
}
return subScriptBits;
}
std::string FontLanguage::getString() const {
if (isUnsupported()) {
return "und";
}
char buf[16];
size_t i = unpackLanguageOrRegion(mLanguage, buf, 'a', 'a');
if (mScript != 0) {
buf[i++] = '-';
buf[i++] = (mScript >> 24) & 0xFFu;
buf[i++] = (mScript >> 16) & 0xFFu;
buf[i++] = (mScript >> 8) & 0xFFu;
buf[i++] = mScript & 0xFFu;
}
if (mRegion != INVALID_CODE) {
buf[i++] = '-';
i += unpackLanguageOrRegion(mRegion, buf + i, 'A', '0');
}
return std::string(buf, i);
}
bool FontLanguage::isEqualScript(const FontLanguage& other) const {
return other.mScript == mScript;
}
// static
bool FontLanguage::supportsScript(uint8_t providedBits, uint8_t requestedBits) {
return requestedBits != 0 && (providedBits & requestedBits) == requestedBits;
}
bool FontLanguage::supportsHbScript(hb_script_t script) const {
static_assert(SCRIPT_TAG('J', 'p', 'a', 'n') == HB_TAG('J', 'p', 'a', 'n'),
"The Minikin script and HarfBuzz hb_script_t have different encodings.");
if (script == mScript) return true;
return supportsScript(mSubScriptBits, scriptToSubScriptBits(script));
}
int FontLanguage::calcScoreFor(const FontLanguages& supported) const {
bool languageScriptMatch = false;
bool subtagMatch = false;
bool scriptMatch = false;
for (size_t i = 0; i < supported.size(); ++i) {
if (mEmojiStyle != EMSTYLE_EMPTY &&
mEmojiStyle == supported[i].mEmojiStyle) {
subtagMatch = true;
if (mLanguage == supported[i].mLanguage) {
return 4;
}
}
if (isEqualScript(supported[i]) ||
supportsScript(supported[i].mSubScriptBits, mSubScriptBits)) {
scriptMatch = true;
if (mLanguage == supported[i].mLanguage) {
languageScriptMatch = true;
}
}
}
if (supportsScript(supported.getUnionOfSubScriptBits(), mSubScriptBits)) {
scriptMatch = true;
if (mLanguage == supported[0].mLanguage && supported.isAllTheSameLanguage()) {
return 3;
}
}
if (languageScriptMatch) {
return 3;
} else if (subtagMatch) {
return 2;
} else if (scriptMatch) {
return 1;
}
return 0;
}
FontLanguages::FontLanguages(std::vector<FontLanguage>&& languages)
: mLanguages(std::move(languages)) {
if (mLanguages.empty()) {
return;
}
const FontLanguage& lang = mLanguages[0];
mIsAllTheSameLanguage = true;
mUnionOfSubScriptBits = lang.mSubScriptBits;
for (size_t i = 1; i < mLanguages.size(); ++i) {
mUnionOfSubScriptBits |= mLanguages[i].mSubScriptBits;
if (mIsAllTheSameLanguage && lang.mLanguage != mLanguages[i].mLanguage) {
mIsAllTheSameLanguage = false;
}
}
}
#undef SCRIPT_TAG
} // namespace minikin

View File

@@ -0,0 +1,158 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_FONT_LANGUAGE_H
#define MINIKIN_FONT_LANGUAGE_H
#include <string>
#include <vector>
#include <hb.h>
namespace minikin {
// Due to the limits in font fallback score calculation, we can't use anything more than 12
// languages.
const size_t FONT_LANGUAGES_LIMIT = 12;
// The language or region code is encoded to 15 bits.
const uint16_t INVALID_CODE = 0x7fff;
class FontLanguages;
// FontLanguage is a compact representation of a BCP 47 language tag. It
// does not capture all possible information, only what directly affects
// font rendering.
struct FontLanguage {
public:
enum EmojiStyle : uint8_t {
EMSTYLE_EMPTY = 0,
EMSTYLE_DEFAULT = 1,
EMSTYLE_EMOJI = 2,
EMSTYLE_TEXT = 3,
};
// Default constructor creates the unsupported language.
FontLanguage()
: mScript(0ul),
mLanguage(INVALID_CODE),
mRegion(INVALID_CODE),
mHbLanguage(HB_LANGUAGE_INVALID),
mSubScriptBits(0ul),
mEmojiStyle(EMSTYLE_EMPTY) {}
// Parse from string
FontLanguage(const char* buf, size_t length);
bool operator==(const FontLanguage other) const {
return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage &&
mRegion == other.mRegion && mEmojiStyle == other.mEmojiStyle;
}
bool operator!=(const FontLanguage other) const {
return !(*this == other);
}
bool isUnsupported() const { return mLanguage == INVALID_CODE; }
EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
hb_language_t getHbLanguage() const { return mHbLanguage; }
bool isEqualScript(const FontLanguage& other) const;
// Returns true if this script supports the given script. For example, ja-Jpan supports Hira,
// ja-Hira doesn't support Jpan.
bool supportsHbScript(hb_script_t script) const;
std::string getString() const;
// Calculates a matching score. This score represents how well the input languages cover this
// language. The maximum score in the language list is returned.
// 0 = no match, 1 = script match, 2 = script and primary language match.
int calcScoreFor(const FontLanguages& supported) const;
uint64_t getIdentifier() const {
return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 17) | ((uint64_t)mRegion << 2) |
mEmojiStyle;
}
private:
friend class FontLanguages; // for FontLanguages constructor
// ISO 15924 compliant script code. The 4 chars script code are packed into a 32 bit integer.
uint32_t mScript;
// ISO 639-1 or ISO 639-2 compliant language code.
// The two- or three-letter language code is packed into a 15 bit integer.
// mLanguage = 0 means the FontLanguage is unsupported.
uint16_t mLanguage;
// ISO 3166-1 or UN M.49 compliant region code. The two-letter or three-digit region code is
// packed into a 15 bit integer.
uint16_t mRegion;
// The language to be passed HarfBuzz shaper.
hb_language_t mHbLanguage;
// For faster comparing, use 7 bits for specific scripts.
static const uint8_t kBopomofoFlag = 1u;
static const uint8_t kHanFlag = 1u << 1;
static const uint8_t kHangulFlag = 1u << 2;
static const uint8_t kHiraganaFlag = 1u << 3;
static const uint8_t kKatakanaFlag = 1u << 4;
static const uint8_t kSimplifiedChineseFlag = 1u << 5;
static const uint8_t kTraditionalChineseFlag = 1u << 6;
uint8_t mSubScriptBits;
EmojiStyle mEmojiStyle;
static uint8_t scriptToSubScriptBits(uint32_t script);
static EmojiStyle resolveEmojiStyle(const char* buf, size_t length, uint32_t script);
// Returns true if the provide subscript bits has the requested subscript bits.
// Note that this function returns false if the requested subscript bits are empty.
static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
};
// An immutable list of languages.
class FontLanguages {
public:
explicit FontLanguages(std::vector<FontLanguage>&& languages);
FontLanguages() : mUnionOfSubScriptBits(0), mIsAllTheSameLanguage(false) {}
FontLanguages(FontLanguages&&) = default;
size_t size() const { return mLanguages.size(); }
bool empty() const { return mLanguages.empty(); }
const FontLanguage& operator[] (size_t n) const { return mLanguages[n]; }
private:
friend struct FontLanguage; // for calcScoreFor
std::vector<FontLanguage> mLanguages;
uint8_t mUnionOfSubScriptBits;
bool mIsAllTheSameLanguage;
uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
bool isAllTheSameLanguage() const { return mIsAllTheSameLanguage; }
// Do not copy and assign.
FontLanguages(const FontLanguages&) = delete;
void operator=(const FontLanguages&) = delete;
};
} // namespace minikin
#endif // MINIKIN_FONT_LANGUAGE_H

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "Minikin"
#include "FontLanguageListCache.h"
#include <unicode/uloc.h>
#include <unordered_set>
#include <log/log.h>
#include "FontLanguage.h"
#include "MinikinInternal.h"
namespace minikin {
const uint32_t FontLanguageListCache::kEmptyListId;
// Returns the text length of output.
static size_t toLanguageTag(char* output, size_t outSize, const std::string& locale) {
output[0] = '\0';
if (locale.empty()) {
return 0;
}
size_t outLength = 0;
UErrorCode uErr = U_ZERO_ERROR;
outLength = uloc_canonicalize(locale.c_str(), output, outSize, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_canonicalize(\"%s\") failed: %s", locale.c_str(), u_errorName(uErr));
output[0] = '\0';
return 0;
}
// Preserve "und" and "und-****" since uloc_addLikelySubtags changes "und" to "en-Latn-US".
if (strncmp(output, "und", 3) == 0 &&
(outLength == 3 || (outLength == 8 && output[3] == '_'))) {
return outLength;
}
char likelyChars[ULOC_FULLNAME_CAPACITY];
uErr = U_ZERO_ERROR;
uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", output, u_errorName(uErr));
output[0] = '\0';
return 0;
}
uErr = U_ZERO_ERROR;
outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, u_errorName(uErr));
output[0] = '\0';
return 0;
}
#ifdef VERBOSE_DEBUG
ALOGD("ICU normalized '%s' to '%s'", locale.c_str(), output);
#endif
return outLength;
}
static std::vector<FontLanguage> parseLanguageList(const std::string& input) {
std::vector<FontLanguage> result;
size_t currentIdx = 0;
size_t commaLoc = 0;
char langTag[ULOC_FULLNAME_CAPACITY];
std::unordered_set<uint64_t> seen;
std::string locale(input.size(), 0);
while ((commaLoc = input.find_first_of(',', currentIdx)) != std::string::npos) {
locale.assign(input, currentIdx, commaLoc - currentIdx);
currentIdx = commaLoc + 1;
size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
FontLanguage lang(langTag, length);
uint64_t identifier = lang.getIdentifier();
if (!lang.isUnsupported() && seen.count(identifier) == 0) {
result.push_back(lang);
if (result.size() == FONT_LANGUAGES_LIMIT) {
break;
}
seen.insert(identifier);
}
}
if (result.size() < FONT_LANGUAGES_LIMIT) {
locale.assign(input, currentIdx, input.size() - currentIdx);
size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
FontLanguage lang(langTag, length);
uint64_t identifier = lang.getIdentifier();
if (!lang.isUnsupported() && seen.count(identifier) == 0) {
result.push_back(lang);
}
}
return result;
}
// static
uint32_t FontLanguageListCache::getId(const std::string& languages) {
FontLanguageListCache* inst = FontLanguageListCache::getInstance();
std::unordered_map<std::string, uint32_t>::const_iterator it =
inst->mLanguageListLookupTable.find(languages);
if (it != inst->mLanguageListLookupTable.end()) {
return it->second;
}
// Given language list is not in cache. Insert it and return newly assigned ID.
const uint32_t nextId = inst->mLanguageLists.size();
FontLanguages fontLanguages(parseLanguageList(languages));
if (fontLanguages.empty()) {
return kEmptyListId;
}
inst->mLanguageLists.push_back(std::move(fontLanguages));
inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));
return nextId;
}
// static
const FontLanguages& FontLanguageListCache::getById(uint32_t id) {
FontLanguageListCache* inst = FontLanguageListCache::getInstance();
LOG_ALWAYS_FATAL_IF(id >= inst->mLanguageLists.size(), "Lookup by unknown language list ID.");
return inst->mLanguageLists[id];
}
// static
FontLanguageListCache* FontLanguageListCache::getInstance() {
assertMinikinLocked();
static FontLanguageListCache* instance = nullptr;
if (instance == nullptr) {
instance = new FontLanguageListCache();
// Insert an empty language list for mapping default language list to kEmptyListId.
// The default language list has only one FontLanguage and it is the unsupported language.
instance->mLanguageLists.push_back(FontLanguages());
instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId));
}
return instance;
}
} // namespace minikin

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_FONT_LANGUAGE_LIST_CACHE_H
#define MINIKIN_FONT_LANGUAGE_LIST_CACHE_H
#include <unordered_map>
#include <minikin/FontFamily.h>
#include "FontLanguage.h"
namespace minikin {
class FontLanguageListCache {
public:
// A special ID for the empty language list.
// This value must be 0 since the empty language list is inserted into mLanguageLists by
// default.
const static uint32_t kEmptyListId = 0;
// Returns language list ID for the given string representation of FontLanguages.
// Caller should acquire a lock before calling the method.
static uint32_t getId(const std::string& languages);
// Caller should acquire a lock before calling the method.
static const FontLanguages& getById(uint32_t id);
private:
FontLanguageListCache() {} // Singleton
~FontLanguageListCache() {}
// Caller should acquire a lock before calling the method.
static FontLanguageListCache* getInstance();
std::vector<FontLanguages> mLanguageLists;
// A map from string representation of the font language list to the ID.
std::unordered_map<std::string, uint32_t> mLanguageListLookupTable;
};
} // namespace minikin
#endif // MINIKIN_FONT_LANGUAGE_LIST_CACHE_H

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#include <stdlib.h>
#include <stdint.h>
#include "FontUtils.h"
namespace minikin {
static uint16_t readU16(const uint8_t* data, size_t offset) {
return data[offset] << 8 | data[offset + 1];
}
static uint32_t readU32(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
}
bool analyzeStyle(const uint8_t* os2_data, size_t os2_size, int* weight, bool* italic) {
const size_t kUsWeightClassOffset = 4;
const size_t kFsSelectionOffset = 62;
const uint16_t kItalicFlag = (1 << 0);
if (os2_size < kFsSelectionOffset + 2) {
return false;
}
uint16_t weightClass = readU16(os2_data, kUsWeightClassOffset);
*weight = weightClass / 100;
uint16_t fsSelection = readU16(os2_data, kFsSelectionOffset);
*italic = (fsSelection & kItalicFlag) != 0;
return true;
}
void analyzeAxes(const uint8_t* fvar_data, size_t fvar_size, std::unordered_set<uint32_t>* axes) {
const size_t kMajorVersionOffset = 0;
const size_t kMinorVersionOffset = 2;
const size_t kOffsetToAxesArrayOffset = 4;
const size_t kAxisCountOffset = 8;
const size_t kAxisSizeOffset = 10;
axes->clear();
if (fvar_size < kAxisSizeOffset + 2) {
return;
}
const uint16_t majorVersion = readU16(fvar_data, kMajorVersionOffset);
const uint16_t minorVersion = readU16(fvar_data, kMinorVersionOffset);
const uint32_t axisOffset = readU16(fvar_data, kOffsetToAxesArrayOffset);
const uint32_t axisCount = readU16(fvar_data, kAxisCountOffset);
const uint32_t axisSize = readU16(fvar_data, kAxisSizeOffset);
if (majorVersion != 1 || minorVersion != 0 || axisOffset != 0x10 || axisSize != 0x14) {
return; // Unsupported version.
}
if (fvar_size < axisOffset + axisOffset * axisCount) {
return; // Invalid table size.
}
for (uint32_t i = 0; i < axisCount; ++i) {
size_t axisRecordOffset = axisOffset + i * axisSize;
uint32_t tag = readU32(fvar_data, axisRecordOffset);
axes->insert(tag);
}
}
} // namespace minikin

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_FONT_UTILS_H
#define MINIKIN_FONT_UTILS_H
#include <unordered_set>
namespace minikin {
bool analyzeStyle(const uint8_t* os2_data, size_t os2_size, int* weight, bool* italic);
void analyzeAxes(const uint8_t* fvar_data, size_t fvar_size, std::unordered_set<uint32_t>* axes);
} // namespace minikin
#endif // MINIKIN_ANALYZE_STYLE_H

View File

@@ -0,0 +1,241 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.
*/
#include <stdint.h>
#include <algorithm>
#include <unicode/uchar.h>
#include <unicode/utf16.h>
#include <minikin/GraphemeBreak.h>
#include <minikin/Emoji.h>
#include "MinikinInternal.h"
namespace minikin {
int32_t tailoredGraphemeClusterBreak(uint32_t c) {
// Characters defined as Control that we want to treat them as Extend.
// These are curated manually.
if (c == 0x00AD // SHY
|| c == 0x061C // ALM
|| c == 0x180E // MONGOLIAN VOWEL SEPARATOR
|| c == 0x200B // ZWSP
|| c == 0x200E // LRM
|| c == 0x200F // RLM
|| (0x202A <= c && c <= 0x202E) // LRE, RLE, PDF, LRO, RLO
|| ((c | 0xF) == 0x206F) // WJ, invisible math operators, LRI, RLI, FSI, PDI,
// and the deprecated invisible format controls
|| c == 0xFEFF // BOM
|| ((c | 0x7F) == 0xE007F)) // recently undeprecated tag characters in Plane 14
return U_GCB_EXTEND;
// THAI CHARACTER SARA AM is treated as a normal letter by most other implementations: they
// allow a grapheme break before it.
else if (c == 0x0E33)
return U_GCB_OTHER;
else
return u_getIntPropertyValue(c, UCHAR_GRAPHEME_CLUSTER_BREAK);
}
// Returns true for all characters whose IndicSyllabicCategory is Pure_Killer.
// From http://www.unicode.org/Public/9.0.0/ucd/IndicSyllabicCategory.txt
bool isPureKiller(uint32_t c) {
return (c == 0x0E3A || c == 0x0E4E || c == 0x0F84 || c == 0x103A || c == 0x1714 || c == 0x1734
|| c == 0x17D1 || c == 0x1BAA || c == 0x1BF2 || c == 0x1BF3 || c == 0xA806
|| c == 0xA953 || c == 0xABED || c == 0x11134 || c == 0x112EA || c == 0x1172B);
}
bool GraphemeBreak::isGraphemeBreak(const float* advances, const uint16_t* buf, size_t start,
size_t count, const size_t offset) {
// This implementation closely follows Unicode Standard Annex #29 on
// Unicode Text Segmentation (http://www.unicode.org/reports/tr29/),
// implementing a tailored version of extended grapheme clusters.
// The GB rules refer to section 3.1.1, Grapheme Cluster Boundary Rules.
// Rule GB1, sot ÷; Rule GB2, ÷ eot
if (offset <= start || offset >= start + count) {
return true;
}
if (U16_IS_TRAIL(buf[offset])) {
// Don't break a surrogate pair, but a lonely trailing surrogate pair is a break
return !U16_IS_LEAD(buf[offset - 1]);
}
uint32_t c1 = 0;
uint32_t c2 = 0;
size_t offset_back = offset;
size_t offset_forward = offset;
U16_PREV(buf, start, offset_back, c1);
U16_NEXT(buf, offset_forward, start + count, c2);
int32_t p1 = tailoredGraphemeClusterBreak(c1);
int32_t p2 = tailoredGraphemeClusterBreak(c2);
// Rule GB3, CR x LF
if (p1 == U_GCB_CR && p2 == U_GCB_LF) {
return false;
}
// Rule GB4, (Control | CR | LF) ÷
if (p1 == U_GCB_CONTROL || p1 == U_GCB_CR || p1 == U_GCB_LF) {
return true;
}
// Rule GB5, ÷ (Control | CR | LF)
if (p2 == U_GCB_CONTROL || p2 == U_GCB_CR || p2 == U_GCB_LF) {
return true;
}
// Rule GB6, L x ( L | V | LV | LVT )
if (p1 == U_GCB_L && (p2 == U_GCB_L || p2 == U_GCB_V || p2 == U_GCB_LV || p2 == U_GCB_LVT)) {
return false;
}
// Rule GB7, ( LV | V ) x ( V | T )
if ((p1 == U_GCB_LV || p1 == U_GCB_V) && (p2 == U_GCB_V || p2 == U_GCB_T)) {
return false;
}
// Rule GB8, ( LVT | T ) x T
if ((p1 == U_GCB_LVT || p1 == U_GCB_T) && p2 == U_GCB_T) {
return false;
}
// Rule GB9, x (Extend | ZWJ); Rule GB9a, x SpacingMark; Rule GB9b, Prepend x
// TODO(abarth): Add U_GCB_ZWJ once we update ICU.
if (p2 == U_GCB_EXTEND || /* p2 == U_GCB_ZWJ || */ p2 == U_GCB_SPACING_MARK || p1 == U_GCB_PREPEND) {
return false;
}
// This is used to decide font-dependent grapheme clusters. If we don't have the advance
// information, we become conservative in grapheme breaking and assume that it has no advance.
const bool c2_has_advance = (advances != nullptr && advances[offset - start] != 0.0);
// All the following rules are font-dependent, in the way that if we know c2 has an advance,
// we definitely know that it cannot form a grapheme with the character(s) before it. So we
// make the decision in favor a grapheme break early.
if (c2_has_advance) {
return true;
}
// Note: For Rule GB10 and GB11 below, we do not use the Unicode line breaking properties for
// determining emoji-ness and carry our own data, because our data could be more fresh than what
// ICU provides.
//
// Tailored version of Rule GB10, (E_Base | EBG) Extend* × E_Modifier.
// The rule itself says do not break between emoji base and emoji modifiers, skipping all Extend
// characters. Variation selectors are considered Extend, so they are handled fine.
//
// We tailor this by requiring that an actual ligature is formed. If the font doesn't form a
// ligature, we allow a break before the modifier.
if (isEmojiModifier(c2)) {
uint32_t c0 = c1;
size_t offset_backback = offset_back;
int32_t p0 = p1;
if (p0 == U_GCB_EXTEND && offset_backback > start) {
// skip over emoji variation selector
U16_PREV(buf, start, offset_backback, c0);
p0 = tailoredGraphemeClusterBreak(c0);
}
if (isEmojiBase(c0)) {
return false;
}
}
// TODO(abarth): Enablet his code once we update ICU.
// Tailored version of Rule GB11, ZWJ × (Glue_After_Zwj | EBG)
// We try to make emoji sequences with ZWJ a single grapheme cluster, but only if they actually
// merge to one cluster. So we are more relaxed than the UAX #29 rules in accepting any emoji
// character after the ZWJ, but are tighter in that we only treat it as one cluster if a
// ligature is actually formed and we also require the character before the ZWJ to also be an
// emoji.
// if (p1 == U_GCB_ZWJ && isEmoji(c2) && offset_back > start) {
// // look at character before ZWJ to see that both can participate in an emoji zwj sequence
// uint32_t c0 = 0;
// size_t offset_backback = offset_back;
// U16_PREV(buf, start, offset_backback, c0);
// if (c0 == 0xFE0F && offset_backback > start) {
// // skip over emoji variation selector
// U16_PREV(buf, start, offset_backback, c0);
// }
// if (isEmoji(c0)) {
// return false;
// }
// }
// Tailored version of Rule GB12 and Rule GB13 that look at even-odd cases.
// sot (RI RI)* RI x RI
// [^RI] (RI RI)* RI x RI
//
// If we have font information, we have already broken the cluster if and only if the second
// character had no advance, which means a ligature was formed. If we don't, we look back like
// UAX #29 recommends, but only up to 1000 code units.
if (p1 == U_GCB_REGIONAL_INDICATOR && p2 == U_GCB_REGIONAL_INDICATOR) {
if (advances != nullptr) {
// We have advances information. But if we are here, we already know c2 has no advance.
// So we should definitely disallow a break.
return false;
} else {
// Look at up to 1000 code units.
const size_t lookback_barrier = std::max((ssize_t)start, (ssize_t)offset_back - 1000);
size_t offset_backback = offset_back;
while (offset_backback > lookback_barrier) {
uint32_t c0 = 0;
U16_PREV(buf, lookback_barrier, offset_backback, c0);
if (tailoredGraphemeClusterBreak(c0) != U_GCB_REGIONAL_INDICATOR) {
offset_backback += U16_LENGTH(c0);
break;
}
}
// The number 4 comes from the number of code units in a whole flag.
return (offset - offset_backback) % 4 == 0;
}
}
// Cluster Indic syllables together (tailoring of UAX #29).
// Immediately after each virama (that is not just a pure killer) followed by a letter, we
// disallow grapheme breaks (if we are here, we don't know about advances, or we already know
// that c2 has no advance).
if (u_getIntPropertyValue(c1, UCHAR_CANONICAL_COMBINING_CLASS) == 9 // virama
&& !isPureKiller(c1)
&& u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) {
return false;
}
// Rule GB999, Any ÷ Any
return true;
}
size_t GraphemeBreak::getTextRunCursor(const float* advances, const uint16_t* buf, size_t start,
size_t count, size_t offset, MoveOpt opt) {
switch (opt) {
case AFTER:
if (offset < start + count) {
offset++;
}
// fall through
case AT_OR_AFTER:
while (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset++;
}
break;
case BEFORE:
if (offset > start) {
offset--;
}
// fall through
case AT_OR_BEFORE:
while (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset--;
}
break;
case AT:
if (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset = (size_t)-1;
}
break;
}
return offset;
}
} // namespace minikin

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "Minikin"
#include "HbFontCache.h"
#include <log/log.h>
#include <utils/LruCache.h>
#include <hb.h>
#include <hb-ot.h>
#include <minikin/MinikinFont.h>
#include "MinikinInternal.h"
namespace minikin {
class HbFontCache : private android::OnEntryRemoved<int32_t, hb_font_t*> {
public:
HbFontCache() : mCache(kMaxEntries) {
mCache.setOnEntryRemovedListener(this);
}
// callback for OnEntryRemoved
void operator()(int32_t& /* key */, hb_font_t*& value) {
hb_font_destroy(value);
}
hb_font_t* get(int32_t fontId) {
return mCache.get(fontId);
}
void put(int32_t fontId, hb_font_t* font) {
mCache.put(fontId, font);
}
void clear() {
mCache.clear();
}
void remove(int32_t fontId) {
mCache.remove(fontId);
}
private:
static const size_t kMaxEntries = 100;
android::LruCache<int32_t, hb_font_t*> mCache;
};
HbFontCache* getFontCacheLocked() {
assertMinikinLocked();
static HbFontCache* cache = nullptr;
if (cache == nullptr) {
cache = new HbFontCache();
}
return cache;
}
void purgeHbFontCacheLocked() {
assertMinikinLocked();
getFontCacheLocked()->clear();
}
void purgeHbFontLocked(const MinikinFont* minikinFont) {
assertMinikinLocked();
const int32_t fontId = minikinFont->GetUniqueId();
getFontCacheLocked()->remove(fontId);
}
// Returns a new reference to a hb_font_t object, caller is
// responsible for calling hb_font_destroy() on it.
hb_font_t* getHbFontLocked(const MinikinFont* minikinFont) {
assertMinikinLocked();
// TODO: get rid of nullFaceFont
static hb_font_t* nullFaceFont = nullptr;
if (minikinFont == nullptr) {
if (nullFaceFont == nullptr) {
nullFaceFont = hb_font_create(nullptr);
}
return hb_font_reference(nullFaceFont);
}
HbFontCache* fontCache = getFontCacheLocked();
const int32_t fontId = minikinFont->GetUniqueId();
hb_font_t* font = fontCache->get(fontId);
if (font != nullptr) {
return hb_font_reference(font);
}
hb_face_t* face = minikinFont->CreateHarfBuzzFace();
hb_font_t* parent_font = hb_font_create(face);
hb_ot_font_set_funcs(parent_font);
unsigned int upem = hb_face_get_upem(face);
hb_font_set_scale(parent_font, upem, upem);
font = hb_font_create_sub_font(parent_font);
// TODO(abarth): Enable this code once we update harfbuzz.
// std::vector<hb_variation_t> variations;
// for (const FontVariation& variation : minikinFont->GetAxes()) {
// variations.push_back({variation.axisTag, variation.value});
// }
// hb_font_set_variations(font, variations.data(), variations.size());
hb_font_destroy(parent_font);
hb_face_destroy(face);
fontCache->put(fontId, font);
return hb_font_reference(font);
}
} // namespace minikin

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_HBFONT_CACHE_H
#define MINIKIN_HBFONT_CACHE_H
struct hb_font_t;
namespace minikin {
class MinikinFont;
void purgeHbFontCacheLocked();
void purgeHbFontLocked(const MinikinFont* minikinFont);
hb_font_t* getHbFontLocked(const MinikinFont* minikinFont);
} // namespace minikin
#endif // MINIKIN_HBFONT_CACHE_H

View File

@@ -0,0 +1,434 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
#include <unicode/uchar.h>
#include <unicode/uscript.h>
// HACK: for reading pattern file
#include <fcntl.h>
#define LOG_TAG "Minikin"
#include "minikin/Hyphenator.h"
using std::vector;
namespace minikin {
static const uint16_t CHAR_HYPHEN_MINUS = 0x002D;
static const uint16_t CHAR_SOFT_HYPHEN = 0x00AD;
static const uint16_t CHAR_MIDDLE_DOT = 0x00B7;
static const uint16_t CHAR_HYPHEN = 0x2010;
// The following are structs that correspond to tables inside the hyb file format
struct AlphabetTable0 {
uint32_t version;
uint32_t min_codepoint;
uint32_t max_codepoint;
uint8_t data[1]; // actually flexible array, size is known at runtime
};
struct AlphabetTable1 {
uint32_t version;
uint32_t n_entries;
uint32_t data[1]; // actually flexible array, size is known at runtime
static uint32_t codepoint(uint32_t entry) { return entry >> 11; }
static uint32_t value(uint32_t entry) { return entry & 0x7ff; }
};
struct Trie {
uint32_t version;
uint32_t char_mask;
uint32_t link_shift;
uint32_t link_mask;
uint32_t pattern_shift;
uint32_t n_entries;
uint32_t data[1]; // actually flexible array, size is known at runtime
};
struct Pattern {
uint32_t version;
uint32_t n_entries;
uint32_t pattern_offset;
uint32_t pattern_size;
uint32_t data[1]; // actually flexible array, size is known at runtime
// accessors
static uint32_t len(uint32_t entry) { return entry >> 26; }
static uint32_t shift(uint32_t entry) { return (entry >> 20) & 0x3f; }
const uint8_t* buf(uint32_t entry) const {
return reinterpret_cast<const uint8_t*>(this) + pattern_offset + (entry & 0xfffff);
}
};
struct Header {
uint32_t magic;
uint32_t version;
uint32_t alphabet_offset;
uint32_t trie_offset;
uint32_t pattern_offset;
uint32_t file_size;
// accessors
const uint8_t* bytes() const { return reinterpret_cast<const uint8_t*>(this); }
uint32_t alphabetVersion() const {
return *reinterpret_cast<const uint32_t*>(bytes() + alphabet_offset);
}
const AlphabetTable0* alphabetTable0() const {
return reinterpret_cast<const AlphabetTable0*>(bytes() + alphabet_offset);
}
const AlphabetTable1* alphabetTable1() const {
return reinterpret_cast<const AlphabetTable1*>(bytes() + alphabet_offset);
}
const Trie* trieTable() const {
return reinterpret_cast<const Trie*>(bytes() + trie_offset);
}
const Pattern* patternTable() const {
return reinterpret_cast<const Pattern*>(bytes() + pattern_offset);
}
};
Hyphenator* Hyphenator::loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix) {
Hyphenator* result = new Hyphenator;
result->patternData = patternData;
result->minPrefix = minPrefix;
result->minSuffix = minSuffix;
return result;
}
void Hyphenator::hyphenate(vector<HyphenationType>* result, const uint16_t* word, size_t len,
const icu::Locale& locale) {
result->clear();
result->resize(len);
const size_t paddedLen = len + 2; // start and stop code each count for 1
if (patternData != nullptr &&
len >= minPrefix + minSuffix && paddedLen <= MAX_HYPHENATED_SIZE) {
uint16_t alpha_codes[MAX_HYPHENATED_SIZE];
const HyphenationType hyphenValue = alphabetLookup(alpha_codes, word, len);
if (hyphenValue != HyphenationType::DONT_BREAK) {
hyphenateFromCodes(result->data(), alpha_codes, paddedLen, hyphenValue);
return;
}
// TODO: try NFC normalization
// TODO: handle non-BMP Unicode (requires remapping of offsets)
}
// Note that we will always get here if the word contains a hyphen or a soft hyphen, because the
// alphabet is not expected to contain a hyphen or a soft hyphen character, so alphabetLookup
// would return DONT_BREAK.
hyphenateWithNoPatterns(result->data(), word, len, locale);
}
// This function determines whether a character is like U+2010 HYPHEN in
// line breaking and usage: a character immediately after which line breaks
// are allowed, but words containing it should not be automatically
// hyphenated using patterns. This is a curated set, created by manually
// inspecting all the characters that have the Unicode line breaking
// property of BA or HY and seeing which ones are hyphens.
bool Hyphenator::isLineBreakingHyphen(uint32_t c) {
return (c == 0x002D || // HYPHEN-MINUS
c == 0x058A || // ARMENIAN HYPHEN
c == 0x05BE || // HEBREW PUNCTUATION MAQAF
c == 0x1400 || // CANADIAN SYLLABICS HYPHEN
c == 0x2010 || // HYPHEN
c == 0x2013 || // EN DASH
c == 0x2027 || // HYPHENATION POINT
c == 0x2E17 || // DOUBLE OBLIQUE HYPHEN
c == 0x2E40); // DOUBLE HYPHEN
}
const static uint32_t HYPHEN_STR[] = {0x2010, 0};
const static uint32_t ARMENIAN_HYPHEN_STR[] = {0x058A, 0};
const static uint32_t MAQAF_STR[] = {0x05BE, 0};
const static uint32_t UCAS_HYPHEN_STR[] = {0x1400, 0};
const static uint32_t ZWJ_STR[] = {0x200D, 0};
const static uint32_t ZWJ_AND_HYPHEN_STR[] = {0x200D, 0x2010, 0};
const uint32_t* HyphenEdit::getHyphenString(uint32_t hyph) {
switch (hyph) {
case INSERT_HYPHEN_AT_END:
case REPLACE_WITH_HYPHEN_AT_END:
case INSERT_HYPHEN_AT_START:
return HYPHEN_STR;
case INSERT_ARMENIAN_HYPHEN_AT_END:
return ARMENIAN_HYPHEN_STR;
case INSERT_MAQAF_AT_END:
return MAQAF_STR;
case INSERT_UCAS_HYPHEN_AT_END:
return UCAS_HYPHEN_STR;
case INSERT_ZWJ_AND_HYPHEN_AT_END:
return ZWJ_AND_HYPHEN_STR;
case INSERT_ZWJ_AT_START:
return ZWJ_STR;
default:
return nullptr;
}
}
uint32_t HyphenEdit::editForThisLine(HyphenationType type) {
switch (type) {
case HyphenationType::DONT_BREAK:
return NO_EDIT;
case HyphenationType::BREAK_AND_INSERT_HYPHEN:
return INSERT_HYPHEN_AT_END;
case HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN:
return INSERT_ARMENIAN_HYPHEN_AT_END;
case HyphenationType::BREAK_AND_INSERT_MAQAF:
return INSERT_MAQAF_AT_END;
case HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN:
return INSERT_UCAS_HYPHEN_AT_END;
case HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN:
return REPLACE_WITH_HYPHEN_AT_END;
case HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ:
return INSERT_ZWJ_AND_HYPHEN_AT_END;
default:
return BREAK_AT_END;
}
}
uint32_t HyphenEdit::editForNextLine(HyphenationType type) {
switch (type) {
case HyphenationType::DONT_BREAK:
return NO_EDIT;
case HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE:
return INSERT_HYPHEN_AT_START;
case HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ:
return INSERT_ZWJ_AT_START;
default:
return BREAK_AT_START;
}
}
static UScriptCode getScript(uint32_t codePoint) {
UErrorCode errorCode = U_ZERO_ERROR;
const UScriptCode script = uscript_getScript(static_cast<UChar32>(codePoint), &errorCode);
if (U_SUCCESS(errorCode)) {
return script;
} else {
return USCRIPT_INVALID_CODE;
}
}
static HyphenationType hyphenationTypeBasedOnScript(uint32_t codePoint) {
// Note: It's not clear what the best hyphen for Hebrew is. While maqaf is the "correct" hyphen
// for Hebrew, modern practice may have shifted towards Western hyphens. We use normal hyphens
// for now to be safe. BREAK_AND_INSERT_MAQAF is already implemented, so if we want to switch
// to maqaf for Hebrew, we can simply add a condition here.
const UScriptCode script = getScript(codePoint);
if (script == USCRIPT_KANNADA
|| script == USCRIPT_MALAYALAM
|| script == USCRIPT_TAMIL
|| script == USCRIPT_TELUGU) {
// Grantha is not included, since we don't support non-BMP hyphenation yet.
return HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
} else if (script == USCRIPT_ARMENIAN) {
return HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN;
} else if (script == USCRIPT_CANADIAN_ABORIGINAL) {
return HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN;
} else {
return HyphenationType::BREAK_AND_INSERT_HYPHEN;
}
}
static inline int32_t getJoiningType(UChar32 codepoint) {
return u_getIntPropertyValue(codepoint, UCHAR_JOINING_TYPE);
}
// Assumption for caller: location must be >= 2 and word[location] == CHAR_SOFT_HYPHEN.
// This function decides if the letters before and after the hyphen should appear as joining.
static inline HyphenationType getHyphTypeForArabic(const uint16_t* word, size_t len,
size_t location) {
ssize_t i = location;
int32_t type = U_JT_NON_JOINING;
while (static_cast<size_t>(i) < len && (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {
i++;
}
if (type == U_JT_DUAL_JOINING || type == U_JT_RIGHT_JOINING || type == U_JT_JOIN_CAUSING) {
// The next character is of the type that may join the last character. See if the last
// character is also of the right type.
i = location - 2; // Skip the soft hyphen
type = U_JT_NON_JOINING;
while (i >= 0 && (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {
i--;
}
if (type == U_JT_DUAL_JOINING || type == U_JT_LEFT_JOINING || type == U_JT_JOIN_CAUSING) {
return HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ;
}
}
return HyphenationType::BREAK_AND_INSERT_HYPHEN;
}
// Use various recommendations of UAX #14 Unicode Line Breaking Algorithm for hyphenating words
// that didn't match patterns, especially words that contain hyphens or soft hyphens (See sections
// 5.3, Use of Hyphen, and 5.4, Use of Soft Hyphen).
void Hyphenator::hyphenateWithNoPatterns(HyphenationType* result, const uint16_t* word, size_t len,
const icu::Locale& locale) {
result[0] = HyphenationType::DONT_BREAK;
for (size_t i = 1; i < len; i++) {
const uint16_t prevChar = word[i - 1];
if (i > 1 && isLineBreakingHyphen(prevChar)) {
// Break after hyphens, but only if they don't start the word.
if ((prevChar == CHAR_HYPHEN_MINUS || prevChar == CHAR_HYPHEN)
&& strcmp(locale.getLanguage(), "pl") == 0
&& getScript(word[i]) == USCRIPT_LATIN ) {
// In Polish, hyphens get repeated at the next line. To be safe,
// we will do this only if the next character is Latin.
result[i] = HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE;
} else {
result[i] = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
}
} else if (i > 1 && prevChar == CHAR_SOFT_HYPHEN) {
// Break after soft hyphens, but only if they don't start the word (a soft hyphen
// starting the word doesn't give any useful break opportunities). The type of the break
// is based on the script of the character we break on.
if (getScript(word[i]) == USCRIPT_ARABIC) {
// For Arabic, we need to look and see if the characters around the soft hyphen
// actually join. If they don't, we'll just insert a normal hyphen.
result[i] = getHyphTypeForArabic(word, len, i);
} else {
result[i] = hyphenationTypeBasedOnScript(word[i]);
}
} else if (prevChar == CHAR_MIDDLE_DOT
&& minPrefix < i && i <= len - minSuffix
&& ((word[i - 2] == 'l' && word[i] == 'l')
|| (word[i - 2] == 'L' && word[i] == 'L'))
&& strcmp(locale.getLanguage(), "ca") == 0) {
// In Catalan, "l·l" should break as "l-" on the first line
// and "l" on the next line.
result[i] = HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN;
} else {
result[i] = HyphenationType::DONT_BREAK;
}
}
}
HyphenationType Hyphenator::alphabetLookup(uint16_t* alpha_codes, const uint16_t* word,
size_t len) {
const Header* header = getHeader();
HyphenationType result = HyphenationType::BREAK_AND_INSERT_HYPHEN;
// TODO: check header magic
uint32_t alphabetVersion = header->alphabetVersion();
if (alphabetVersion == 0) {
const AlphabetTable0* alphabet = header->alphabetTable0();
uint32_t min_codepoint = alphabet->min_codepoint;
uint32_t max_codepoint = alphabet->max_codepoint;
alpha_codes[0] = 0; // word start
for (size_t i = 0; i < len; i++) {
uint16_t c = word[i];
if (c < min_codepoint || c >= max_codepoint) {
return HyphenationType::DONT_BREAK;
}
uint8_t code = alphabet->data[c - min_codepoint];
if (code == 0) {
return HyphenationType::DONT_BREAK;
}
if (result == HyphenationType::BREAK_AND_INSERT_HYPHEN) {
result = hyphenationTypeBasedOnScript(c);
}
alpha_codes[i + 1] = code;
}
alpha_codes[len + 1] = 0; // word termination
return result;
} else if (alphabetVersion == 1) {
const AlphabetTable1* alphabet = header->alphabetTable1();
size_t n_entries = alphabet->n_entries;
const uint32_t* begin = alphabet->data;
const uint32_t* end = begin + n_entries;
alpha_codes[0] = 0;
for (size_t i = 0; i < len; i++) {
uint16_t c = word[i];
auto p = std::lower_bound(begin, end, c << 11);
if (p == end) {
return HyphenationType::DONT_BREAK;
}
uint32_t entry = *p;
if (AlphabetTable1::codepoint(entry) != c) {
return HyphenationType::DONT_BREAK;
}
if (result == HyphenationType::BREAK_AND_INSERT_HYPHEN) {
result = hyphenationTypeBasedOnScript(c);
}
alpha_codes[i + 1] = AlphabetTable1::value(entry);
}
alpha_codes[len + 1] = 0;
return result;
}
return HyphenationType::DONT_BREAK;
}
/**
* Internal implementation, after conversion to codes. All case folding and normalization
* has been done by now, and all characters have been found in the alphabet.
* Note: len here is the padded length including 0 codes at start and end.
**/
void Hyphenator::hyphenateFromCodes(HyphenationType* result, const uint16_t* codes, size_t len,
HyphenationType hyphenValue) {
static_assert(sizeof(HyphenationType) == sizeof(uint8_t), "HyphnationType must be uint8_t.");
// Reuse the result array as a buffer for calculating intermediate hyphenation numbers.
uint8_t* buffer = reinterpret_cast<uint8_t*>(result);
const Header* header = getHeader();
const Trie* trie = header->trieTable();
const Pattern* pattern = header->patternTable();
uint32_t char_mask = trie->char_mask;
uint32_t link_shift = trie->link_shift;
uint32_t link_mask = trie->link_mask;
uint32_t pattern_shift = trie->pattern_shift;
size_t maxOffset = len - minSuffix - 1;
for (size_t i = 0; i < len - 1; i++) {
uint32_t node = 0; // index into Trie table
for (size_t j = i; j < len; j++) {
uint16_t c = codes[j];
uint32_t entry = trie->data[node + c];
if ((entry & char_mask) == c) {
node = (entry & link_mask) >> link_shift;
} else {
break;
}
uint32_t pat_ix = trie->data[node] >> pattern_shift;
// pat_ix contains a 3-tuple of length, shift (number of trailing zeros), and an offset
// into the buf pool. This is the pattern for the substring (i..j) we just matched,
// which we combine (via point-wise max) into the buffer vector.
if (pat_ix != 0) {
uint32_t pat_entry = pattern->data[pat_ix];
int pat_len = Pattern::len(pat_entry);
int pat_shift = Pattern::shift(pat_entry);
const uint8_t* pat_buf = pattern->buf(pat_entry);
int offset = j + 1 - (pat_len + pat_shift);
// offset is the index within buffer that lines up with the start of pat_buf
int start = std::max((int)minPrefix - offset, 0);
int end = std::min(pat_len, (int)maxOffset - offset);
for (int k = start; k < end; k++) {
buffer[offset + k] = std::max(buffer[offset + k], pat_buf[k]);
}
}
}
}
// Since the above calculation does not modify values outside
// [minPrefix, len - minSuffix], they are left as 0 = DONT_BREAK.
for (size_t i = minPrefix; i < maxOffset; i++) {
// Hyphenation opportunities happen when the hyphenation numbers are odd.
result[i] = (buffer[i] & 1u) ? hyphenValue : HyphenationType::DONT_BREAK;
}
}
} // namespace minikin

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "Minikin"
#include "LayoutUtils.h"
namespace minikin {
const uint16_t CHAR_NBSP = 0x00A0;
/*
* Determine whether the code unit is a word space for the purposes of justification.
*/
bool isWordSpace(uint16_t code_unit) {
return code_unit == ' ' || code_unit == CHAR_NBSP;
}
/**
* For the purpose of layout, a word break is a boundary with no
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
static bool isWordBreakAfter(uint16_t c) {
if (isWordSpace(c) || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) {
// spaces
return true;
}
// Note: kana is not included, as sophisticated fonts may kern kana
return false;
}
static bool isWordBreakBefore(uint16_t c) {
// CJK ideographs (and yijing hexagram symbols)
return isWordBreakAfter(c) || (c >= 0x3400 && c <= 0x9fff);
}
/**
* Return offset of previous word break. It is either < offset or == 0.
*/
size_t getPrevWordBreakForCache(
const uint16_t* chars, size_t offset, size_t len) {
if (offset == 0) return 0;
if (offset > len) offset = len;
if (isWordBreakBefore(chars[offset - 1])) {
return offset - 1;
}
for (size_t i = offset - 1; i > 0; i--) {
if (isWordBreakBefore(chars[i]) || isWordBreakAfter(chars[i - 1])) {
return i;
}
}
return 0;
}
/**
* Return offset of next word break. It is either > offset or == len.
*/
size_t getNextWordBreakForCache(
const uint16_t* chars, size_t offset, size_t len) {
if (offset >= len) return len;
if (isWordBreakAfter(chars[offset])) {
return offset + 1;
}
for (size_t i = offset + 1; i < len; i++) {
// No need to check isWordBreakAfter(chars[i - 1]) since it is checked
// in previous iteration. Note that isWordBreakBefore returns true
// whenever isWordBreakAfter returns true.
if (isWordBreakBefore(chars[i])) {
return i;
}
}
return len;
}
} // namespace minikin

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#ifndef MINIKIN_LAYOUT_UTILS_H
#define MINIKIN_LAYOUT_UTILS_H
#include <stddef.h>
#include <stdint.h>
namespace minikin {
/*
* Determine whether the code unit is a word space for the purposes of justification.
*/
bool isWordSpace(uint16_t code_unit);
/**
* Return offset of previous word break. It is either < offset or == 0.
*
* For the purpose of layout, a word break is a boundary with no
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
size_t getPrevWordBreakForCache(
const uint16_t* chars, size_t offset, size_t len);
/**
* Return offset of next word break. It is either > offset or == len.
*
* For the purpose of layout, a word break is a boundary with no
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
size_t getNextWordBreakForCache(
const uint16_t* chars, size_t offset, size_t len);
} // namespace minikin
#endif // MINIKIN_LAYOUT_UTILS_H

View File

@@ -0,0 +1,523 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#define VERBOSE_DEBUG 0
#define LOG_TAG "Minikin"
#include <limits>
#include <log/log.h>
#include "LayoutUtils.h"
#include <minikin/Layout.h>
#include <minikin/LineBreaker.h>
using std::vector;
namespace minikin {
const int CHAR_TAB = 0x0009;
// Large scores in a hierarchy; we prefer desperate breaks to an overfull line. All these
// constants are larger than any reasonable actual width score.
const float SCORE_INFTY = std::numeric_limits<float>::max();
const float SCORE_OVERFULL = 1e12f;
const float SCORE_DESPERATE = 1e10f;
// Multiplier for hyphen penalty on last line.
const float LAST_LINE_PENALTY_MULTIPLIER = 4.0f;
// Penalty assigned to each line break (to try to minimize number of lines)
// TODO: when we implement full justification (so spaces can shrink and stretch), this is
// probably not the most appropriate method.
const float LINE_PENALTY_MULTIPLIER = 2.0f;
// Penalty assigned to shrinking the whitepsace.
const float SHRINK_PENALTY_MULTIPLIER = 4.0f;
// Very long words trigger O(n^2) behavior in hyphenation, so we disable hyphenation for
// unreasonably long words. This is somewhat of a heuristic because extremely long words
// are possible in some languages. This does mean that very long real words can get
// broken by desperate breaks, with no hyphens.
const size_t LONGEST_HYPHENATED_WORD = 45;
// When the text buffer is within this limit, capacity of vectors is retained at finish(),
// to avoid allocation.
const size_t MAX_TEXT_BUF_RETAIN = 32678;
// Maximum amount that spaces can shrink, in justified text.
const float SHRINKABILITY = 1.0 / 3.0;
void LineBreaker::setLocale(const icu::Locale& locale, Hyphenator* hyphenator) {
mWordBreaker.setLocale(locale);
mLocale = locale;
mHyphenator = hyphenator;
}
void LineBreaker::setText() {
mWordBreaker.setText(mTextBuf.data(), mTextBuf.size());
// handle initial break here because addStyleRun may never be called
mWordBreaker.next();
mCandidates.clear();
Candidate cand = {0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, HyphenationType::DONT_BREAK};
mCandidates.push_back(cand);
// reset greedy breaker state
mBreaks.clear();
mWidths.clear();
mFlags.clear();
mLastBreak = 0;
mBestBreak = 0;
mBestScore = SCORE_INFTY;
mPreBreak = 0;
mLastHyphenation = HyphenEdit::NO_EDIT;
mFirstTabIndex = INT_MAX;
mSpaceCount = 0;
}
void LineBreaker::setLineWidths(float firstWidth, int firstWidthLineCount, float restWidth) {
mLineWidths.setWidths(firstWidth, firstWidthLineCount, restWidth);
}
void LineBreaker::setIndents(const std::vector<float>& indents) {
mLineWidths.setIndents(indents);
}
// This function determines whether a character is a space that disappears at end of line.
// It is the Unicode set: [[:General_Category=Space_Separator:]-[:Line_Break=Glue:]],
// plus '\n'.
// Note: all such characters are in the BMP, so it's ok to use code units for this.
static bool isLineEndSpace(uint16_t c) {
return c == '\n' || c == ' ' || c == 0x1680 || (0x2000 <= c && c <= 0x200A && c != 0x2007) ||
c == 0x205F || c == 0x3000;
}
// Ordinarily, this method measures the text in the range given. However, when paint
// is nullptr, it assumes the widths have already been calculated and stored in the
// width buffer.
// This method finds the candidate word breaks (using the ICU break iterator) and sends them
// to addCandidate.
float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface,
FontStyle style, size_t start, size_t end, bool isRtl, double letterSpacing) {
float width = 0.0f;
int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
float hyphenPenalty = 0.0;
if (paint != nullptr) {
width = Layout::measureText(mTextBuf.data(), start, end - start, mTextBuf.size(), bidiFlags,
style, *paint, typeface, mCharWidths.data() + start);
// a heuristic that seems to perform well
hyphenPenalty = 0.5 * paint->size * paint->scaleX * mLineWidths.getLineWidth(0);
if (mHyphenationFrequency == kHyphenationFrequency_Normal) {
hyphenPenalty *= 4.0; // TODO: Replace with a better value after some testing
}
if (mJustified) {
// Make hyphenation more aggressive for fully justified text (so that "normal" in
// justified mode is the same as "full" in ragged-right).
hyphenPenalty *= 0.25;
} else {
// Line penalty is zero for justified text.
mLinePenalty = std::max(mLinePenalty, hyphenPenalty * LINE_PENALTY_MULTIPLIER);
}
}
size_t current = (size_t)mWordBreaker.current();
size_t afterWord = start;
size_t lastBreak = start;
ParaWidth lastBreakWidth = mWidth;
ParaWidth postBreak = mWidth;
size_t postSpaceCount = mSpaceCount;
for (size_t i = start; i < end; i++) {
uint16_t c = mTextBuf[i];
if (c == CHAR_TAB) {
mWidth = mPreBreak + mTabStops.nextTab(mWidth - mPreBreak);
if (mFirstTabIndex == INT_MAX) {
mFirstTabIndex = (int)i;
}
// fall back to greedy; other modes don't know how to deal with tabs
mStrategy = kBreakStrategy_Greedy;
} else {
if (isWordSpace(c)) mSpaceCount += 1;
mWidth += mCharWidths[i];
if (c == '\n') mWidth += INT_MAX;
if (!isLineEndSpace(c)) {
postBreak = mWidth;
postSpaceCount = mSpaceCount;
afterWord = i + 1;
}
}
if (i + 1 == current) {
size_t wordStart = mWordBreaker.wordStart();
size_t wordEnd = mWordBreaker.wordEnd();
if (paint != nullptr && mHyphenator != nullptr &&
mHyphenationFrequency != kHyphenationFrequency_None &&
wordStart >= start && wordEnd > wordStart &&
wordEnd - wordStart <= LONGEST_HYPHENATED_WORD) {
mHyphenator->hyphenate(&mHyphBuf,
&mTextBuf[wordStart],
wordEnd - wordStart,
mLocale);
#if VERBOSE_DEBUG
std::string hyphenatedString;
for (size_t j = wordStart; j < wordEnd; j++) {
if (mHyphBuf[j - wordStart] == HyphenationType::BREAK_AND_INSERT_HYPHEN) {
hyphenatedString.push_back('-');
}
// Note: only works with ASCII, should do UTF-8 conversion here
hyphenatedString.push_back(buffer()[j]);
}
ALOGD("hyphenated string: %s", hyphenatedString.c_str());
#endif
// measure hyphenated substrings
for (size_t j = wordStart; j < wordEnd; j++) {
HyphenationType hyph = mHyphBuf[j - wordStart];
if (hyph != HyphenationType::DONT_BREAK) {
paint->hyphenEdit = HyphenEdit::editForThisLine(hyph);
const float firstPartWidth = Layout::measureText(mTextBuf.data(),
lastBreak, j - lastBreak, mTextBuf.size(), bidiFlags, style,
*paint, typeface, nullptr);
ParaWidth hyphPostBreak = lastBreakWidth + firstPartWidth;
paint->hyphenEdit = HyphenEdit::editForNextLine(hyph);
const float secondPartWidth = Layout::measureText(mTextBuf.data(), j,
afterWord - j, mTextBuf.size(), bidiFlags, style, *paint,
typeface, nullptr);
ParaWidth hyphPreBreak = postBreak - secondPartWidth;
addWordBreak(j, hyphPreBreak, hyphPostBreak, postSpaceCount, postSpaceCount,
hyphenPenalty, hyph);
paint->hyphenEdit = HyphenEdit::NO_EDIT;
}
}
}
// Skip break for zero-width characters inside replacement span
if (paint != nullptr || current == end || mCharWidths[current] > 0) {
float penalty = hyphenPenalty * mWordBreaker.breakBadness();
addWordBreak(current, mWidth, postBreak, mSpaceCount, postSpaceCount, penalty,
HyphenationType::DONT_BREAK);
}
lastBreak = current;
lastBreakWidth = mWidth;
current = (size_t)mWordBreaker.next();
}
}
return width;
}
// add a word break (possibly for a hyphenated fragment), and add desperate breaks if
// needed (ie when word exceeds current line width)
void LineBreaker::addWordBreak(size_t offset, ParaWidth preBreak, ParaWidth postBreak,
size_t preSpaceCount, size_t postSpaceCount, float penalty, HyphenationType hyph) {
Candidate cand;
ParaWidth width = mCandidates.back().preBreak;
if (postBreak - width > currentLineWidth()) {
// Add desperate breaks.
// Note: these breaks are based on the shaping of the (non-broken) original text; they
// are imprecise especially in the presence of kerning, ligatures, and Arabic shaping.
size_t i = mCandidates.back().offset;
width += mCharWidths[i++];
for (; i < offset; i++) {
float w = mCharWidths[i];
if (w > 0) {
cand.offset = i;
cand.preBreak = width;
cand.postBreak = width;
// postSpaceCount doesn't include trailing spaces
cand.preSpaceCount = postSpaceCount;
cand.postSpaceCount = postSpaceCount;
cand.penalty = SCORE_DESPERATE;
cand.hyphenType = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
#if VERBOSE_DEBUG
ALOGD("desperate cand: %zd %g:%g",
mCandidates.size(), cand.postBreak, cand.preBreak);
#endif
addCandidate(cand);
width += w;
}
}
}
cand.offset = offset;
cand.preBreak = preBreak;
cand.postBreak = postBreak;
cand.penalty = penalty;
cand.preSpaceCount = preSpaceCount;
cand.postSpaceCount = postSpaceCount;
cand.hyphenType = hyph;
#if VERBOSE_DEBUG
ALOGD("cand: %zd %g:%g", mCandidates.size(), cand.postBreak, cand.preBreak);
#endif
addCandidate(cand);
}
// Helper method for addCandidate()
void LineBreaker::pushGreedyBreak() {
const Candidate& bestCandidate = mCandidates[mBestBreak];
pushBreak(bestCandidate.offset, bestCandidate.postBreak - mPreBreak,
mLastHyphenation | HyphenEdit::editForThisLine(bestCandidate.hyphenType));
mBestScore = SCORE_INFTY;
#if VERBOSE_DEBUG
ALOGD("break: %d %g", mBreaks.back(), mWidths.back());
#endif
mLastBreak = mBestBreak;
mPreBreak = bestCandidate.preBreak;
mLastHyphenation = HyphenEdit::editForNextLine(bestCandidate.hyphenType);
}
// TODO performance: could avoid populating mCandidates if greedy only
void LineBreaker::addCandidate(Candidate cand) {
const size_t candIndex = mCandidates.size();
mCandidates.push_back(cand);
// mLastBreak is the index of the last line break we decided to do in mCandidates,
// and mPreBreak is its preBreak value. mBestBreak is the index of the best line breaking candidate
// we have found since then, and mBestScore is its penalty.
if (cand.postBreak - mPreBreak > currentLineWidth()) {
// This break would create an overfull line, pick the best break and break there (greedy)
if (mBestBreak == mLastBreak) {
// No good break has been found since last break. Break here.
mBestBreak = candIndex;
}
pushGreedyBreak();
}
while (mLastBreak != candIndex && cand.postBreak - mPreBreak > currentLineWidth()) {
// We should rarely come here. But if we are here, we have broken the line, but the
// remaining part still doesn't fit. We now need to break at the second best place after the
// last break, but we have not kept that information, so we need to go back and find it.
//
// In some really rare cases, postBreak - preBreak of a candidate itself may be over the
// current line width. We protect ourselves against an infinite loop in that case by
// checking that we have not broken the line at this candidate already.
for (size_t i = mLastBreak + 1; i < candIndex; i++) {
const float penalty = mCandidates[i].penalty;
if (penalty <= mBestScore) {
mBestBreak = i;
mBestScore = penalty;
}
}
if (mBestBreak == mLastBreak) {
// We didn't find anything good. Break here.
mBestBreak = candIndex;
}
pushGreedyBreak();
}
if (cand.penalty <= mBestScore) {
mBestBreak = candIndex;
mBestScore = cand.penalty;
}
}
void LineBreaker::pushBreak(int offset, float width, uint8_t hyphenEdit) {
mBreaks.push_back(offset);
mWidths.push_back(width);
int flags = (mFirstTabIndex < mBreaks.back()) << kTab_Shift;
flags |= hyphenEdit;
mFlags.push_back(flags);
mFirstTabIndex = INT_MAX;
}
void LineBreaker::addReplacement(size_t start, size_t end, float width) {
mCharWidths[start] = width;
std::fill(&mCharWidths[start + 1], &mCharWidths[end], 0.0f);
addStyleRun(nullptr, nullptr, FontStyle(), start, end, false, 0);
}
// Get the width of a space. May return 0 if there are no spaces.
// Note: if there are multiple different widths for spaces (for example, because of mixing of
// fonts), it's only guaranteed to pick one.
float LineBreaker::getSpaceWidth() const {
for (size_t i = 0; i < mTextBuf.size(); i++) {
if (isWordSpace(mTextBuf[i])) {
return mCharWidths[i];
}
}
return 0.0f;
}
float LineBreaker::currentLineWidth() const {
return mLineWidths.getLineWidth(mBreaks.size());
}
void LineBreaker::computeBreaksGreedy() {
// All breaks but the last have been added in addCandidate already.
size_t nCand = mCandidates.size();
if (nCand > 0 && (nCand == 1 || mLastBreak != nCand - 1)) {
pushBreak(mCandidates[nCand - 1].offset, mCandidates[nCand - 1].postBreak - mPreBreak,
mLastHyphenation);
// don't need to update mBestScore, because we're done
#if VERBOSE_DEBUG
ALOGD("final break: %d %g", mBreaks.back(), mWidths.back());
#endif
}
}
// Follow "prev" links in mCandidates array, and copy to result arrays.
void LineBreaker::finishBreaksOptimal() {
// clear existing greedy break result
mBreaks.clear();
mWidths.clear();
mFlags.clear();
size_t nCand = mCandidates.size();
size_t prev;
for (size_t i = nCand - 1; i > 0; i = prev) {
prev = mCandidates[i].prev;
mBreaks.push_back(mCandidates[i].offset);
mWidths.push_back(mCandidates[i].postBreak - mCandidates[prev].preBreak);
int flags = HyphenEdit::editForThisLine(mCandidates[i].hyphenType);
if (prev > 0) {
flags |= HyphenEdit::editForNextLine(mCandidates[prev].hyphenType);
}
mFlags.push_back(flags);
}
std::reverse(mBreaks.begin(), mBreaks.end());
std::reverse(mWidths.begin(), mWidths.end());
std::reverse(mFlags.begin(), mFlags.end());
}
void LineBreaker::computeBreaksOptimal(bool isRectangle) {
size_t active = 0;
size_t nCand = mCandidates.size();
float width = mLineWidths.getLineWidth(0);
float shortLineFactor = mJustified ? 0.75f : 0.5f;
float maxShrink = mJustified ? SHRINKABILITY * getSpaceWidth() : 0.0f;
// "i" iterates through candidates for the end of the line.
for (size_t i = 1; i < nCand; i++) {
bool atEnd = i == nCand - 1;
float best = SCORE_INFTY;
size_t bestPrev = 0;
size_t lineNumberLast = 0;
if (!isRectangle) {
size_t lineNumberLast = mCandidates[active].lineNumber;
width = mLineWidths.getLineWidth(lineNumberLast);
}
ParaWidth leftEdge = mCandidates[i].postBreak - width;
float bestHope = 0;
// "j" iterates through candidates for the beginning of the line.
for (size_t j = active; j < i; j++) {
if (!isRectangle) {
size_t lineNumber = mCandidates[j].lineNumber;
if (lineNumber != lineNumberLast) {
float widthNew = mLineWidths.getLineWidth(lineNumber);
if (widthNew != width) {
leftEdge = mCandidates[i].postBreak - width;
bestHope = 0;
width = widthNew;
}
lineNumberLast = lineNumber;
}
}
float jScore = mCandidates[j].score;
if (jScore + bestHope >= best) continue;
float delta = mCandidates[j].preBreak - leftEdge;
// compute width score for line
// Note: the "bestHope" optimization makes the assumption that, when delta is
// non-negative, widthScore will increase monotonically as successive candidate
// breaks are considered.
float widthScore = 0.0f;
float additionalPenalty = 0.0f;
if ((atEnd || !mJustified) && delta < 0) {
widthScore = SCORE_OVERFULL;
} else if (atEnd && mStrategy != kBreakStrategy_Balanced) {
// increase penalty for hyphen on last line
additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * mCandidates[j].penalty;
// Penalize very short (< 1 - shortLineFactor of total width) lines.
float underfill = delta - shortLineFactor * width;
widthScore = underfill > 0 ? underfill * underfill : 0;
} else {
widthScore = delta * delta;
if (delta < 0) {
if (-delta < maxShrink *
(mCandidates[i].postSpaceCount - mCandidates[j].preSpaceCount)) {
widthScore *= SHRINK_PENALTY_MULTIPLIER;
} else {
widthScore = SCORE_OVERFULL;
}
}
}
if (delta < 0) {
active = j + 1;
} else {
bestHope = widthScore;
}
float score = jScore + widthScore + additionalPenalty;
if (score <= best) {
best = score;
bestPrev = j;
}
}
mCandidates[i].score = best + mCandidates[i].penalty + mLinePenalty;
mCandidates[i].prev = bestPrev;
mCandidates[i].lineNumber = mCandidates[bestPrev].lineNumber + 1;
#if VERBOSE_DEBUG
ALOGD("break %zd: score=%g, prev=%zd", i, mCandidates[i].score, mCandidates[i].prev);
#endif
}
finishBreaksOptimal();
}
size_t LineBreaker::computeBreaks() {
if (mStrategy == kBreakStrategy_Greedy) {
computeBreaksGreedy();
} else {
computeBreaksOptimal(mLineWidths.isConstant());
}
return mBreaks.size();
}
void LineBreaker::finish() {
mWordBreaker.finish();
mWidth = 0;
mLineWidths.clear();
mCandidates.clear();
mBreaks.clear();
mWidths.clear();
mFlags.clear();
if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) {
mTextBuf.clear();
mTextBuf.shrink_to_fit();
mCharWidths.clear();
mCharWidths.shrink_to_fit();
mHyphBuf.clear();
mHyphBuf.shrink_to_fit();
mCandidates.shrink_to_fit();
mBreaks.shrink_to_fit();
mWidths.shrink_to_fit();
mFlags.shrink_to_fit();
}
mStrategy = kBreakStrategy_Greedy;
mHyphenationFrequency = kHyphenationFrequency_Normal;
mLinePenalty = 0.0f;
mJustified = false;
}
} // namespace minikin

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "Minikin"
#include <cmath>
#include <unicode/uchar.h>
#include <log/log.h>
#include <minikin/GraphemeBreak.h>
#include <minikin/Measurement.h>
namespace minikin {
// These could be considered helper methods of layout, but need only be loosely coupled, so
// are separate.
static float getRunAdvance(const float* advances, const uint16_t* buf, size_t layoutStart,
size_t start, size_t count, size_t offset) {
float advance = 0.0f;
size_t lastCluster = start;
float clusterWidth = 0.0f;
for (size_t i = start; i < offset; i++) {
float charAdvance = advances[i - layoutStart];
if (charAdvance != 0.0f) {
advance += charAdvance;
lastCluster = i;
clusterWidth = charAdvance;
}
}
if (offset < start + count && advances[offset - layoutStart] == 0.0f) {
// In the middle of a cluster, distribute width of cluster so that each grapheme cluster
// gets an equal share.
// TODO: get caret information out of font when that's available
size_t nextCluster;
for (nextCluster = offset + 1; nextCluster < start + count; nextCluster++) {
if (advances[nextCluster - layoutStart] != 0.0f) break;
}
int numGraphemeClusters = 0;
int numGraphemeClustersAfter = 0;
for (size_t i = lastCluster; i < nextCluster; i++) {
bool isAfter = i >= offset;
if (GraphemeBreak::isGraphemeBreak(
advances + (start - layoutStart), buf, start, count, i)) {
numGraphemeClusters++;
if (isAfter) {
numGraphemeClustersAfter++;
}
}
}
if (numGraphemeClusters > 0) {
advance -= clusterWidth * numGraphemeClustersAfter / numGraphemeClusters;
}
}
return advance;
}
float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
size_t offset) {
return getRunAdvance(advances, buf, start, start, count, offset);
}
/**
* Essentially the inverse of getRunAdvance. Compute the value of offset for which the
* measured caret comes closest to the provided advance param, and which is on a grapheme
* cluster boundary.
*
* The actual implementation fast-forwards through clusters to get "close", then does a finer-grain
* search within the cluster and grapheme breaks.
*/
size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
float advance) {
float x = 0.0f, xLastClusterStart = 0.0f, xSearchStart = 0.0f;
size_t lastClusterStart = start, searchStart = start;
for (size_t i = start; i < start + count; i++) {
if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
searchStart = lastClusterStart;
xSearchStart = xLastClusterStart;
}
float width = advances[i - start];
if (width != 0.0f) {
lastClusterStart = i;
xLastClusterStart = x;
x += width;
if (x > advance) {
break;
}
}
}
size_t best = searchStart;
float bestDist = FLT_MAX;
for (size_t i = searchStart; i <= start + count; i++) {
if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
// "getRunAdvance(layout, buf, start, count, i) - advance" but more efficient
float delta = getRunAdvance(advances, buf, start, searchStart, count - searchStart, i)
+ xSearchStart - advance;
if (std::abs(delta) < bestDist) {
bestDist = std::abs(delta);
best = i;
}
if (delta >= 0.0f) {
break;
}
}
}
return best;
}
} // namespace minikin

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.
*/
#include <minikin/MinikinFont.h>
#include "HbFontCache.h"
#include "MinikinInternal.h"
namespace minikin {
MinikinFont::~MinikinFont() {
std::lock_guard<std::mutex> _l(gMinikinLock);
purgeHbFontLocked(this);
}
} // namespace minikin

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.
*/
// Definitions internal to Minikin
#define LOG_TAG "Minikin"
#include "MinikinInternal.h"
#include "HbFontCache.h"
#include <log/log.h>
namespace minikin {
std::mutex gMinikinLock;
void assertMinikinLocked() {
#ifdef ENABLE_RACE_DETECTION
LOG_ALWAYS_FATAL_IF(gMinikinLock.tryLock() == 0);
#endif
}
hb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag) {
assertMinikinLocked();
hb_font_t* font = getHbFontLocked(minikinFont);
hb_face_t* face = hb_font_get_face(font);
hb_blob_t* blob = hb_face_reference_table(face, tag);
hb_font_destroy(font);
return blob;
}
} // namespace minikin

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.
*/
// Definitions internal to Minikin
#ifndef MINIKIN_INTERNAL_H
#define MINIKIN_INTERNAL_H
#include <mutex>
#include <hb.h>
#include <minikin/MinikinFont.h>
namespace minikin {
// All external Minikin interfaces are designed to be thread-safe.
// Presently, that's implemented by through a global lock, and having
// all external interfaces take that lock.
extern std::mutex gMinikinLock;
// Aborts if gMinikinLock is not acquired. Do nothing on the release build.
void assertMinikinLocked();
hb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag);
constexpr uint32_t MAX_UNICODE_CODE_POINT = 0x10FFFF;
// An RAII wrapper for hb_blob_t
class HbBlob {
public:
// Takes ownership of hb_blob_t object, caller is no longer
// responsible for calling hb_blob_destroy().
explicit HbBlob(hb_blob_t* blob) : mBlob(blob) {
}
~HbBlob() {
hb_blob_destroy(mBlob);
}
const uint8_t* get() const {
const char* data = hb_blob_get_data(mBlob, nullptr);
return reinterpret_cast<const uint8_t*>(data);
}
size_t size() const {
return (size_t)hb_blob_get_length(mBlob);
}
private:
hb_blob_t* mBlob;
};
} // namespace minikin
#endif // MINIKIN_INTERNAL_H

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "SparseBitSet"
#include <stddef.h>
#include <string.h>
#include <log/log.h>
#include <minikin/SparseBitSet.h>
namespace minikin {
const uint32_t SparseBitSet::kNotFound;
uint32_t SparseBitSet::calcNumPages(const uint32_t* ranges, size_t nRanges) {
bool haveZeroPage = false;
uint32_t nonzeroPageEnd = 0;
uint32_t nPages = 0;
for (size_t i = 0; i < nRanges; i++) {
uint32_t start = ranges[i * 2];
uint32_t end = ranges[i * 2 + 1];
uint32_t startPage = start >> kLogValuesPerPage;
uint32_t endPage = (end - 1) >> kLogValuesPerPage;
if (startPage >= nonzeroPageEnd) {
if (startPage > nonzeroPageEnd) {
if (!haveZeroPage) {
haveZeroPage = true;
nPages++;
}
}
nPages++;
}
nPages += endPage - startPage;
nonzeroPageEnd = endPage + 1;
}
return nPages;
}
void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) {
if (nRanges == 0) {
return;
}
const uint32_t maxVal = ranges[nRanges * 2 - 1];
if (maxVal >= kMaximumCapacity) {
return;
}
mMaxVal = maxVal;
mIndices.reset(new uint16_t[(mMaxVal + kPageMask) >> kLogValuesPerPage]);
uint32_t nPages = calcNumPages(ranges, nRanges);
mBitmaps.reset(new element[nPages << (kLogValuesPerPage - kLogBitsPerEl)]());
mZeroPageIndex = noZeroPage;
uint32_t nonzeroPageEnd = 0;
uint32_t currentPage = 0;
for (size_t i = 0; i < nRanges; i++) {
uint32_t start = ranges[i * 2];
uint32_t end = ranges[i * 2 + 1];
LOG_ALWAYS_FATAL_IF(end < start); // make sure range size is nonnegative
uint32_t startPage = start >> kLogValuesPerPage;
uint32_t endPage = (end - 1) >> kLogValuesPerPage;
if (startPage >= nonzeroPageEnd) {
if (startPage > nonzeroPageEnd) {
if (mZeroPageIndex == noZeroPage) {
mZeroPageIndex = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
}
for (uint32_t j = nonzeroPageEnd; j < startPage; j++) {
mIndices[j] = mZeroPageIndex;
}
}
mIndices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
}
size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) +
((start & kPageMask) >> kLogBitsPerEl);
size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl;
if (nElements == 1) {
mBitmaps[index] |= (kElAllOnes >> (start & kElMask)) &
(kElAllOnes << ((~end + 1) & kElMask));
} else {
mBitmaps[index] |= kElAllOnes >> (start & kElMask);
for (size_t j = 1; j < nElements - 1; j++) {
mBitmaps[index + j] = kElAllOnes;
}
mBitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask);
}
for (size_t j = startPage + 1; j < endPage + 1; j++) {
mIndices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
}
nonzeroPageEnd = endPage + 1;
}
}
int SparseBitSet::CountLeadingZeros(element x) {
// Note: GCC / clang builtin
return sizeof(element) <= sizeof(int) ? __builtin_clz(x) : __builtin_clzl(x);
}
uint32_t SparseBitSet::nextSetBit(uint32_t fromIndex) const {
if (fromIndex >= mMaxVal) {
return kNotFound;
}
uint32_t fromPage = fromIndex >> kLogValuesPerPage;
const element* bitmap = &mBitmaps[mIndices[fromPage]];
uint32_t offset = (fromIndex & kPageMask) >> kLogBitsPerEl;
element e = bitmap[offset] & (kElAllOnes >> (fromIndex & kElMask));
if (e != 0) {
return (fromIndex & ~kElMask) + CountLeadingZeros(e);
}
for (uint32_t j = offset + 1; j < (1 << (kLogValuesPerPage - kLogBitsPerEl)); j++) {
e = bitmap[j];
if (e != 0) {
return (fromIndex & ~kPageMask) + (j << kLogBitsPerEl) + CountLeadingZeros(e);
}
}
uint32_t maxPage = (mMaxVal + kPageMask) >> kLogValuesPerPage;
for (uint32_t page = fromPage + 1; page < maxPage; page++) {
uint16_t index = mIndices[page];
if (index == mZeroPageIndex) {
continue;
}
bitmap = &mBitmaps[index];
for (uint32_t j = 0; j < (1 << (kLogValuesPerPage - kLogBitsPerEl)); j++) {
e = bitmap[j];
if (e != 0) {
return (page << kLogValuesPerPage) + (j << kLogBitsPerEl) + CountLeadingZeros(e);
}
}
}
return kNotFound;
}
} // namespace minikin

View File

@@ -0,0 +1,271 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "Minikin"
#include <log/log.h>
#include <minikin/Emoji.h>
#include <minikin/Hyphenator.h>
#include <minikin/WordBreaker.h>
#include "MinikinInternal.h"
#include <unicode/uchar.h>
#include <unicode/utf16.h>
namespace minikin {
const uint32_t CHAR_SOFT_HYPHEN = 0x00AD;
const uint32_t CHAR_ZWJ = 0x200D;
void WordBreaker::setLocale(const icu::Locale& locale) {
UErrorCode status = U_ZERO_ERROR;
mBreakIterator.reset(icu::BreakIterator::createLineInstance(locale, status));
// TODO: handle failure status
if (mText != nullptr) {
mBreakIterator->setText(&mUText, status);
}
mIteratorWasReset = true;
}
void WordBreaker::setText(const uint16_t* data, size_t size) {
mText = data;
mTextSize = size;
mIteratorWasReset = false;
mLast = 0;
mCurrent = 0;
mScanOffset = 0;
mInEmailOrUrl = false;
UErrorCode status = U_ZERO_ERROR;
utext_openUChars(&mUText, data, size, &status);
mBreakIterator->setText(&mUText, status);
mBreakIterator->first();
}
ssize_t WordBreaker::current() const {
return mCurrent;
}
/**
* Determine whether a line break at position i within the buffer buf is valid. This
* represents customization beyond the ICU behavior, because plain ICU provides some
* line break opportunities that we don't want.
**/
static bool isBreakValid(const uint16_t* buf, size_t bufEnd, size_t i) {
uint32_t codePoint;
size_t prev_offset = i;
U16_PREV(buf, 0, prev_offset, codePoint);
// Do not break on hard or soft hyphens. These are handled by automatic hyphenation.
if (Hyphenator::isLineBreakingHyphen(codePoint) || codePoint == CHAR_SOFT_HYPHEN) {
// txt addition: Temporarily always break on hyphen. Changed from false to true.
return true;
}
// For Myanmar kinzi sequences, created by <consonant, ASAT, VIRAMA, consonant>. This is to go
// around a bug in ICU line breaking: http://bugs.icu-project.org/trac/ticket/12561. To avoid
// too much looking around in the strings, we simply avoid breaking after any Myanmar virama,
// where no line break could be imagined, since the Myanmar virama is a pure stacker.
if (codePoint == 0x1039) { // MYANMAR SIGN VIRAMA
return false;
}
uint32_t next_codepoint;
size_t next_offset = i;
U16_NEXT(buf, next_offset, bufEnd, next_codepoint);
// Rule LB8 for Emoji ZWJ sequences. We need to do this ourselves since we may have fresher
// emoji data than ICU does.
if (codePoint == CHAR_ZWJ && isEmoji(next_codepoint)) {
return false;
}
// Rule LB30b. We need to this ourselves since we may have fresher emoji data than ICU does.
if (isEmojiModifier(next_codepoint)) {
if (codePoint == 0xFE0F && prev_offset > 0) {
// skip over emoji variation selector
U16_PREV(buf, 0, prev_offset, codePoint);
}
if (isEmojiBase(codePoint)) {
return false;
}
}
return true;
}
// Customized iteratorNext that takes care of both resets and our modifications
// to ICU's behavior.
int32_t WordBreaker::iteratorNext() {
int32_t result;
do {
if (mIteratorWasReset) {
result = mBreakIterator->following(mCurrent);
mIteratorWasReset = false;
} else {
result = mBreakIterator->next();
}
} while (!(result == icu::BreakIterator::DONE || (size_t)result == mTextSize
|| isBreakValid(mText, mTextSize, result)));
return result;
}
// Chicago Manual of Style recommends breaking after these characters in URLs and email addresses
static bool breakAfter(uint16_t c) {
return c == ':' || c == '=' || c == '&';
}
// Chicago Manual of Style recommends breaking before these characters in URLs and email addresses
static bool breakBefore(uint16_t c) {
return c == '~' || c == '.' || c == ',' || c == '-' || c == '_' || c == '?' || c == '#'
|| c == '%' || c == '=' || c == '&';
}
enum ScanState {
START,
SAW_AT,
SAW_COLON,
SAW_COLON_SLASH,
SAW_COLON_SLASH_SLASH,
};
void WordBreaker::detectEmailOrUrl() {
// scan forward from current ICU position for email address or URL
if (mLast >= mScanOffset) {
ScanState state = START;
size_t i;
for (i = mLast; i < mTextSize; i++) {
uint16_t c = mText[i];
// scan only ASCII characters, stop at space
if (!(' ' < c && c <= 0x007E)) {
break;
}
if (state == START && c == '@') {
state = SAW_AT;
} else if (state == START && c == ':') {
state = SAW_COLON;
} else if (state == SAW_COLON || state == SAW_COLON_SLASH) {
if (c == '/') {
state = static_cast<ScanState>((int)state + 1); // next state adds a slash
} else {
state = START;
}
}
}
if (state == SAW_AT || state == SAW_COLON_SLASH_SLASH) {
if (!mBreakIterator->isBoundary(i)) {
// If there are combining marks or such at the end of the URL or the email address,
// consider them a part of the URL or the email, and skip to the next actual
// boundary.
i = mBreakIterator->following(i);
}
mInEmailOrUrl = true;
mIteratorWasReset = true;
} else {
mInEmailOrUrl = false;
}
mScanOffset = i;
}
}
ssize_t WordBreaker::findNextBreakInEmailOrUrl() {
// special rules for email addresses and URL's as per Chicago Manual of Style (16th ed.)
uint16_t lastChar = mText[mLast];
ssize_t i;
for (i = mLast + 1; i < mScanOffset; i++) {
if (breakAfter(lastChar)) {
break;
}
// break after double slash
if (lastChar == '/' && i >= mLast + 2 && mText[i - 2] == '/') {
break;
}
const uint16_t thisChar = mText[i];
// never break after hyphen
if (lastChar != '-') {
if (breakBefore(thisChar)) {
break;
}
// break before single slash
if (thisChar == '/' && lastChar != '/' &&
!(i + 1 < mScanOffset && mText[i + 1] == '/')) {
break;
}
}
lastChar = thisChar;
}
return i;
}
ssize_t WordBreaker::next() {
mLast = mCurrent;
detectEmailOrUrl();
if (mInEmailOrUrl) {
mCurrent = findNextBreakInEmailOrUrl();
} else { // Business as usual
mCurrent = (ssize_t) iteratorNext();
}
return mCurrent;
}
ssize_t WordBreaker::wordStart() const {
if (mInEmailOrUrl) {
return mLast;
}
ssize_t result = mLast;
while (result < mCurrent) {
UChar32 c;
ssize_t ix = result;
U16_NEXT(mText, ix, mCurrent, c);
const int32_t lb = u_getIntPropertyValue(c, UCHAR_LINE_BREAK);
// strip leading punctuation, defined as OP and QU line breaking classes,
// see UAX #14
if (!(lb == U_LB_OPEN_PUNCTUATION || lb == U_LB_QUOTATION)) {
break;
}
result = ix;
}
return result;
}
ssize_t WordBreaker::wordEnd() const {
if (mInEmailOrUrl) {
return mLast;
}
ssize_t result = mCurrent;
while (result > mLast) {
UChar32 c;
ssize_t ix = result;
U16_PREV(mText, mLast, ix, c);
const int32_t gc_mask = U_GET_GC_MASK(c);
// strip trailing space and punctuation
if ((gc_mask & (U_GC_ZS_MASK | U_GC_P_MASK)) == 0) {
break;
}
result = ix;
}
return result;
}
int WordBreaker::breakBadness() const {
return (mInEmailOrUrl && mCurrent < mScanOffset) ? 1 : 0;
}
void WordBreaker::finish() {
mText = nullptr;
// Note: calling utext_close multiply is safe
utext_close(&mUText);
}
} // namespace minikin

View File

@@ -0,0 +1,34 @@
# Copyright 2017 Google Inc.
#
# 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.
config("shims_config") {
include_dirs = [ "." ]
}
source_set("shims") {
sources = [
"log/log.h",
"log/log.cc",
"utils/JenkinsHash.cpp",
"utils/JenkinsHash.h",
"utils/LruCache.h",
"utils/TypeHelpers.h",
]
public_configs = [ ":shims_config" ]
public_deps = [
"//lib/ftl",
]
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include <log/log.h>
int __android_log_error_write(int tag,
const char* subTag,
int32_t uid,
const char* data,
uint32_t dataLen) {
return 0;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#pragma once
#include <stdint.h>
#include "lib/ftl/logging.h"
#ifndef LOG_ALWAYS_FATAL_IF
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
((cond) ? (FTL_LOG(FATAL) << #cond) : (void)0)
#endif
#ifndef LOG_ALWAYS_FATAL
#define LOG_ALWAYS_FATAL(...) FTL_LOG(FATAL)
#endif
#ifndef LOG_ASSERT
#define LOG_ASSERT(cond, ...) FTL_CHECK(cond)
#define ALOG_ASSERT LOG_ASSERT
#endif
#ifndef ALOGD
#define ALOGD(message, ...) FTL_DLOG(INFO) << (message)
#endif
#ifndef ALOGW
#define ALOGW(message, ...) FTL_LOG(WARNING) << (message)
#endif
#ifndef ALOGE
#define ALOGE(message, ...) FTL_LOG(ERROR) << (message)
#endif
#define android_errorWriteLog(tag, subTag) \
__android_log_error_write(tag, subTag, -1, NULL, 0)
#define android_errorWriteWithInfoLog(tag, subTag, uid, data, dataLen) \
__android_log_error_write(tag, subTag, uid, data, dataLen)
int __android_log_error_write(int tag,
const char* subTag,
int32_t uid,
const char* data,
uint32_t dataLen);

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.
*/
/* Implementation of Jenkins one-at-a-time hash function. These choices are
* optimized for code size and portability, rather than raw speed. But speed
* should still be quite good.
**/
#include <stdlib.h>
#include <utils/JenkinsHash.h>
namespace android {
#ifdef __clang__
__attribute__((no_sanitize("integer")))
#endif
hash_t JenkinsHashWhiten(uint32_t hash) {
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
uint32_t JenkinsHashMixBytes(uint32_t hash, const uint8_t* bytes, size_t size) {
if (size > UINT32_MAX) {
abort();
}
hash = JenkinsHashMix(hash, (uint32_t)size);
size_t i;
for (i = 0; i < (size & -4); i += 4) {
uint32_t data = bytes[i] | (bytes[i+1] << 8) | (bytes[i+2] << 16) | (bytes[i+3] << 24);
hash = JenkinsHashMix(hash, data);
}
if (size & 3) {
uint32_t data = bytes[i];
data |= ((size & 3) > 1) ? (bytes[i+1] << 8) : 0;
data |= ((size & 3) > 2) ? (bytes[i+2] << 16) : 0;
hash = JenkinsHashMix(hash, data);
}
return hash;
}
uint32_t JenkinsHashMixShorts(uint32_t hash, const uint16_t* shorts, size_t size) {
if (size > UINT32_MAX) {
abort();
}
hash = JenkinsHashMix(hash, (uint32_t)size);
size_t i;
for (i = 0; i < (size & -2); i += 2) {
uint32_t data = shorts[i] | (shorts[i+1] << 16);
hash = JenkinsHashMix(hash, data);
}
if (size & 1) {
uint32_t data = shorts[i];
hash = JenkinsHashMix(hash, data);
}
return hash;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.
*/
/* Implementation of Jenkins one-at-a-time hash function. These choices are
* optimized for code size and portability, rather than raw speed. But speed
* should still be quite good.
**/
#ifndef ANDROID_JENKINS_HASH_H
#define ANDROID_JENKINS_HASH_H
#include <utils/TypeHelpers.h>
namespace android {
/* The Jenkins hash of a sequence of 32 bit words A, B, C is:
* Whiten(Mix(Mix(Mix(0, A), B), C)) */
#ifdef __clang__
__attribute__((no_sanitize("integer")))
#endif
inline uint32_t JenkinsHashMix(uint32_t hash, uint32_t data) {
hash += data;
hash += (hash << 10);
hash ^= (hash >> 6);
return hash;
}
hash_t JenkinsHashWhiten(uint32_t hash);
/* Helpful utility functions for hashing data in 32 bit chunks */
uint32_t JenkinsHashMixBytes(uint32_t hash, const uint8_t* bytes, size_t size);
uint32_t JenkinsHashMixShorts(uint32_t hash, const uint16_t* shorts, size_t size);
}
#endif // ANDROID_JENKINS_HASH_H

View File

@@ -0,0 +1,298 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.
*/
#ifndef ANDROID_UTILS_LRU_CACHE_H
#define ANDROID_UTILS_LRU_CACHE_H
#include <memory>
#include <unordered_set>
#include "utils/TypeHelpers.h" // hash_t
namespace android {
/**
* GenerationCache callback used when an item is removed
*/
template<typename EntryKey, typename EntryValue>
class OnEntryRemoved {
public:
virtual ~OnEntryRemoved() { };
virtual void operator()(EntryKey& key, EntryValue& value) = 0;
}; // class OnEntryRemoved
template <typename TKey, typename TValue>
class LruCache {
public:
explicit LruCache(uint32_t maxCapacity);
virtual ~LruCache();
enum Capacity {
kUnlimitedCapacity,
};
void setOnEntryRemovedListener(OnEntryRemoved<TKey, TValue>* listener);
size_t size() const;
const TValue& get(const TKey& key);
bool put(const TKey& key, const TValue& value);
bool remove(const TKey& key);
bool removeOldest();
void clear();
const TValue& peekOldestValue();
private:
LruCache(const LruCache& that); // disallow copy constructor
// Super class so that we can have entries having only a key reference, for searches.
class KeyedEntry {
public:
virtual const TKey& getKey() const = 0;
// Make sure the right destructor is executed so that keys and values are deleted.
virtual ~KeyedEntry() {}
};
class Entry final : public KeyedEntry {
public:
TKey key;
TValue value;
Entry* parent;
Entry* child;
Entry(TKey _key, TValue _value) : key(_key), value(_value), parent(NULL), child(NULL) {
}
const TKey& getKey() const final { return key; }
};
class EntryForSearch : public KeyedEntry {
public:
const TKey& key;
EntryForSearch(const TKey& key_) : key(key_) {
}
const TKey& getKey() const final { return key; }
};
struct HashForEntry : public std::unary_function<KeyedEntry*, hash_t> {
size_t operator() (const KeyedEntry* entry) const {
return hash_type(entry->getKey());
};
};
struct EqualityForHashedEntries : public std::unary_function<KeyedEntry*, hash_t> {
bool operator() (const KeyedEntry* lhs, const KeyedEntry* rhs) const {
return lhs->getKey() == rhs->getKey();
};
};
// All entries in the set will be Entry*. Using the weaker KeyedEntry as to allow entries
// that have only a key reference, for searching.
typedef std::unordered_set<KeyedEntry*, HashForEntry, EqualityForHashedEntries> LruCacheSet;
void attachToCache(Entry& entry);
void detachFromCache(Entry& entry);
typename LruCacheSet::iterator findByKey(const TKey& key) {
EntryForSearch entryForSearch(key);
typename LruCacheSet::iterator result = mSet->find(&entryForSearch);
return result;
}
std::unique_ptr<LruCacheSet> mSet;
OnEntryRemoved<TKey, TValue>* mListener;
Entry* mOldest;
Entry* mYoungest;
uint32_t mMaxCapacity;
TValue mNullValue;
public:
// To be used like:
// while (it.next()) {
// it.value(); it.key();
// }
class Iterator {
public:
Iterator(const LruCache<TKey, TValue>& cache):
mCache(cache), mIterator(mCache.mSet->begin()), mBeginReturned(false) {
}
bool next() {
if (mIterator == mCache.mSet->end()) {
return false;
}
if (!mBeginReturned) {
// mIterator has been initialized to the beginning and
// hasn't been returned. Do not advance:
mBeginReturned = true;
} else {
std::advance(mIterator, 1);
}
bool ret = (mIterator != mCache.mSet->end());
return ret;
}
const TValue& value() const {
// All the elements in the set are of type Entry. See comment in the definition
// of LruCacheSet above.
return reinterpret_cast<Entry *>(*mIterator)->value;
}
const TKey& key() const {
return (*mIterator)->getKey();
}
private:
const LruCache<TKey, TValue>& mCache;
typename LruCacheSet::iterator mIterator;
bool mBeginReturned;
};
};
// Implementation is here, because it's fully templated
template <typename TKey, typename TValue>
LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity)
: mSet(new LruCacheSet())
, mListener(NULL)
, mOldest(NULL)
, mYoungest(NULL)
, mMaxCapacity(maxCapacity)
, mNullValue(0) {
mSet->max_load_factor(1.0);
};
template <typename TKey, typename TValue>
LruCache<TKey, TValue>::~LruCache() {
// Need to delete created entries.
clear();
};
template<typename K, typename V>
void LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) {
mListener = listener;
}
template <typename TKey, typename TValue>
size_t LruCache<TKey, TValue>::size() const {
return mSet->size();
}
template <typename TKey, typename TValue>
const TValue& LruCache<TKey, TValue>::get(const TKey& key) {
typename LruCacheSet::const_iterator find_result = findByKey(key);
if (find_result == mSet->end()) {
return mNullValue;
}
// All the elements in the set are of type Entry. See comment in the definition
// of LruCacheSet above.
Entry *entry = reinterpret_cast<Entry*>(*find_result);
detachFromCache(*entry);
attachToCache(*entry);
return entry->value;
}
template <typename TKey, typename TValue>
bool LruCache<TKey, TValue>::put(const TKey& key, const TValue& value) {
if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) {
removeOldest();
}
if (findByKey(key) != mSet->end()) {
return false;
}
Entry* newEntry = new Entry(key, value);
mSet->insert(newEntry);
attachToCache(*newEntry);
return true;
}
template <typename TKey, typename TValue>
bool LruCache<TKey, TValue>::remove(const TKey& key) {
typename LruCacheSet::const_iterator find_result = findByKey(key);
if (find_result == mSet->end()) {
return false;
}
// All the elements in the set are of type Entry. See comment in the definition
// of LruCacheSet above.
Entry* entry = reinterpret_cast<Entry*>(*find_result);
mSet->erase(entry);
if (mListener) {
(*mListener)(entry->key, entry->value);
}
detachFromCache(*entry);
delete entry;
return true;
}
template <typename TKey, typename TValue>
bool LruCache<TKey, TValue>::removeOldest() {
if (mOldest != NULL) {
return remove(mOldest->key);
// TODO: should probably abort if false
}
return false;
}
template <typename TKey, typename TValue>
const TValue& LruCache<TKey, TValue>::peekOldestValue() {
if (mOldest) {
return mOldest->value;
}
return mNullValue;
}
template <typename TKey, typename TValue>
void LruCache<TKey, TValue>::clear() {
if (mListener) {
for (Entry* p = mOldest; p != NULL; p = p->child) {
(*mListener)(p->key, p->value);
}
}
mYoungest = NULL;
mOldest = NULL;
for (auto entry : *mSet.get()) {
delete entry;
}
mSet->clear();
}
template <typename TKey, typename TValue>
void LruCache<TKey, TValue>::attachToCache(Entry& entry) {
if (mYoungest == NULL) {
mYoungest = mOldest = &entry;
} else {
entry.parent = mYoungest;
mYoungest->child = &entry;
mYoungest = &entry;
}
}
template <typename TKey, typename TValue>
void LruCache<TKey, TValue>::detachFromCache(Entry& entry) {
if (entry.parent != NULL) {
entry.parent->child = entry.child;
} else {
mOldest = entry.child;
}
if (entry.child != NULL) {
entry.child->parent = entry.parent;
} else {
mYoungest = entry.parent;
}
entry.parent = NULL;
entry.child = NULL;
}
}
#endif // ANDROID_UTILS_LRU_CACHE_H

View File

@@ -0,0 +1,336 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* 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.
*/
#ifndef ANDROID_TYPE_HELPERS_H
#define ANDROID_TYPE_HELPERS_H
#include <new>
#include <type_traits>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
// ---------------------------------------------------------------------------
namespace android {
/*
* Types traits
*/
template <typename T> struct trait_trivial_ctor { enum { value = false }; };
template <typename T> struct trait_trivial_dtor { enum { value = false }; };
template <typename T> struct trait_trivial_copy { enum { value = false }; };
template <typename T> struct trait_trivial_move { enum { value = false }; };
template <typename T> struct trait_pointer { enum { value = false }; };
template <typename T> struct trait_pointer<T*> { enum { value = true }; };
template <typename TYPE>
struct traits {
enum {
// whether this type is a pointer
is_pointer = trait_pointer<TYPE>::value,
// whether this type's constructor is a no-op
has_trivial_ctor = is_pointer || trait_trivial_ctor<TYPE>::value,
// whether this type's destructor is a no-op
has_trivial_dtor = is_pointer || trait_trivial_dtor<TYPE>::value,
// whether this type type can be copy-constructed with memcpy
has_trivial_copy = is_pointer || trait_trivial_copy<TYPE>::value,
// whether this type can be moved with memmove
has_trivial_move = is_pointer || trait_trivial_move<TYPE>::value
};
};
template <typename T, typename U>
struct aggregate_traits {
enum {
is_pointer = false,
has_trivial_ctor =
traits<T>::has_trivial_ctor && traits<U>::has_trivial_ctor,
has_trivial_dtor =
traits<T>::has_trivial_dtor && traits<U>::has_trivial_dtor,
has_trivial_copy =
traits<T>::has_trivial_copy && traits<U>::has_trivial_copy,
has_trivial_move =
traits<T>::has_trivial_move && traits<U>::has_trivial_move
};
};
#define ANDROID_TRIVIAL_CTOR_TRAIT( T ) \
template<> struct trait_trivial_ctor< T > { enum { value = true }; };
#define ANDROID_TRIVIAL_DTOR_TRAIT( T ) \
template<> struct trait_trivial_dtor< T > { enum { value = true }; };
#define ANDROID_TRIVIAL_COPY_TRAIT( T ) \
template<> struct trait_trivial_copy< T > { enum { value = true }; };
#define ANDROID_TRIVIAL_MOVE_TRAIT( T ) \
template<> struct trait_trivial_move< T > { enum { value = true }; };
#define ANDROID_BASIC_TYPES_TRAITS( T ) \
ANDROID_TRIVIAL_CTOR_TRAIT( T ) \
ANDROID_TRIVIAL_DTOR_TRAIT( T ) \
ANDROID_TRIVIAL_COPY_TRAIT( T ) \
ANDROID_TRIVIAL_MOVE_TRAIT( T )
// ---------------------------------------------------------------------------
/*
* basic types traits
*/
ANDROID_BASIC_TYPES_TRAITS( void )
ANDROID_BASIC_TYPES_TRAITS( bool )
ANDROID_BASIC_TYPES_TRAITS( char )
ANDROID_BASIC_TYPES_TRAITS( unsigned char )
ANDROID_BASIC_TYPES_TRAITS( short )
ANDROID_BASIC_TYPES_TRAITS( unsigned short )
ANDROID_BASIC_TYPES_TRAITS( int )
ANDROID_BASIC_TYPES_TRAITS( unsigned int )
ANDROID_BASIC_TYPES_TRAITS( long )
ANDROID_BASIC_TYPES_TRAITS( unsigned long )
ANDROID_BASIC_TYPES_TRAITS( long long )
ANDROID_BASIC_TYPES_TRAITS( unsigned long long )
ANDROID_BASIC_TYPES_TRAITS( float )
ANDROID_BASIC_TYPES_TRAITS( double )
// ---------------------------------------------------------------------------
/*
* compare and order types
*/
template<typename TYPE> inline
int strictly_order_type(const TYPE& lhs, const TYPE& rhs) {
return (lhs < rhs) ? 1 : 0;
}
template<typename TYPE> inline
int compare_type(const TYPE& lhs, const TYPE& rhs) {
return strictly_order_type(rhs, lhs) - strictly_order_type(lhs, rhs);
}
/*
* create, destroy, copy and move types...
*/
template<typename TYPE> inline
void construct_type(TYPE* p, size_t n) {
if (!traits<TYPE>::has_trivial_ctor) {
while (n > 0) {
n--;
new(p++) TYPE;
}
}
}
template<typename TYPE> inline
void destroy_type(TYPE* p, size_t n) {
if (!traits<TYPE>::has_trivial_dtor) {
while (n > 0) {
n--;
p->~TYPE();
p++;
}
}
}
template<typename TYPE>
typename std::enable_if<traits<TYPE>::has_trivial_copy>::type
inline
copy_type(TYPE* d, const TYPE* s, size_t n) {
memcpy(d,s,n*sizeof(TYPE));
}
template<typename TYPE>
typename std::enable_if<!traits<TYPE>::has_trivial_copy>::type
inline
copy_type(TYPE* d, const TYPE* s, size_t n) {
while (n > 0) {
n--;
new(d) TYPE(*s);
d++, s++;
}
}
template<typename TYPE> inline
void splat_type(TYPE* where, const TYPE* what, size_t n) {
if (!traits<TYPE>::has_trivial_copy) {
while (n > 0) {
n--;
new(where) TYPE(*what);
where++;
}
} else {
while (n > 0) {
n--;
*where++ = *what;
}
}
}
template<typename TYPE>
struct use_trivial_move : public std::integral_constant<bool,
(traits<TYPE>::has_trivial_dtor && traits<TYPE>::has_trivial_copy)
|| traits<TYPE>::has_trivial_move
> {};
template<typename TYPE>
typename std::enable_if<use_trivial_move<TYPE>::value>::type
inline
move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {
memmove(d, s, n*sizeof(TYPE));
}
template<typename TYPE>
typename std::enable_if<!use_trivial_move<TYPE>::value>::type
inline
move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {
d += n;
s += n;
while (n > 0) {
n--;
--d, --s;
if (!traits<TYPE>::has_trivial_copy) {
new(d) TYPE(*s);
} else {
*d = *s;
}
if (!traits<TYPE>::has_trivial_dtor) {
s->~TYPE();
}
}
}
template<typename TYPE>
typename std::enable_if<use_trivial_move<TYPE>::value>::type
inline
move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {
memmove(d, s, n*sizeof(TYPE));
}
template<typename TYPE>
typename std::enable_if<!use_trivial_move<TYPE>::value>::type
inline
move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {
while (n > 0) {
n--;
if (!traits<TYPE>::has_trivial_copy) {
new(d) TYPE(*s);
} else {
*d = *s;
}
if (!traits<TYPE>::has_trivial_dtor) {
s->~TYPE();
}
d++, s++;
}
}
// ---------------------------------------------------------------------------
/*
* a key/value pair
*/
template <typename KEY, typename VALUE>
struct key_value_pair_t {
typedef KEY key_t;
typedef VALUE value_t;
KEY key;
VALUE value;
key_value_pair_t() { }
key_value_pair_t(const key_value_pair_t& o) : key(o.key), value(o.value) { }
key_value_pair_t& operator=(const key_value_pair_t& o) {
key = o.key;
value = o.value;
return *this;
}
key_value_pair_t(const KEY& k, const VALUE& v) : key(k), value(v) { }
explicit key_value_pair_t(const KEY& k) : key(k) { }
inline bool operator < (const key_value_pair_t& o) const {
return strictly_order_type(key, o.key);
}
inline const KEY& getKey() const {
return key;
}
inline const VALUE& getValue() const {
return value;
}
};
template <typename K, typename V>
struct trait_trivial_ctor< key_value_pair_t<K, V> >
{ enum { value = aggregate_traits<K,V>::has_trivial_ctor }; };
template <typename K, typename V>
struct trait_trivial_dtor< key_value_pair_t<K, V> >
{ enum { value = aggregate_traits<K,V>::has_trivial_dtor }; };
template <typename K, typename V>
struct trait_trivial_copy< key_value_pair_t<K, V> >
{ enum { value = aggregate_traits<K,V>::has_trivial_copy }; };
template <typename K, typename V>
struct trait_trivial_move< key_value_pair_t<K, V> >
{ enum { value = aggregate_traits<K,V>::has_trivial_move }; };
// ---------------------------------------------------------------------------
/*
* Hash codes.
*/
typedef uint32_t hash_t;
template <typename TKey>
hash_t hash_type(const TKey& key);
/* Built-in hash code specializations */
#define ANDROID_INT32_HASH(T) \
template <> inline hash_t hash_type(const T& value) { return hash_t(value); }
#define ANDROID_INT64_HASH(T) \
template <> inline hash_t hash_type(const T& value) { \
return hash_t((value >> 32) ^ value); }
#define ANDROID_REINTERPRET_HASH(T, R) \
template <> inline hash_t hash_type(const T& value) { \
R newValue; \
static_assert(sizeof(newValue) == sizeof(value), "size mismatch"); \
memcpy(&newValue, &value, sizeof(newValue)); \
return hash_type(newValue); \
}
ANDROID_INT32_HASH(bool)
ANDROID_INT32_HASH(int8_t)
ANDROID_INT32_HASH(uint8_t)
ANDROID_INT32_HASH(int16_t)
ANDROID_INT32_HASH(uint16_t)
ANDROID_INT32_HASH(int32_t)
ANDROID_INT32_HASH(uint32_t)
ANDROID_INT64_HASH(int64_t)
ANDROID_INT64_HASH(uint64_t)
ANDROID_REINTERPRET_HASH(float, uint32_t)
ANDROID_REINTERPRET_HASH(double, uint64_t)
template <typename T> inline hash_t hash_type(T* const & value) {
return hash_type(uintptr_t(value));
}
}; // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_TYPE_HELPERS_H

View File

@@ -0,0 +1,17 @@
# Copyright 2017 Google Inc.
#
# 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.
source_set("src") {
}

View File

@@ -0,0 +1,249 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "lib/txt/src/font_collection.h"
#include <list>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "lib/ftl/logging.h"
#include "lib/txt/src/font_skia.h"
#include "third_party/skia/include/core/SkString.h"
#include "third_party/skia/include/ports/SkFontMgr.h"
#include "third_party/skia/include/ports/SkFontMgr_android.h"
#include "third_party/skia/include/ports/SkFontMgr_directory.h"
namespace txt {
// TODO(garyq): Will be deprecated when full compatibility with Flutter Engine
// is complete.
FontCollection& FontCollection::GetFontCollection(std::string dir) {
std::vector<std::string> dirs = {dir};
return GetFontCollection(std::move(dirs));
}
// TODO(garyq): Will be deprecated when full compatibility with Flutter Engine
// is complete.
FontCollection& FontCollection::GetFontCollection(
std::vector<std::string> dirs) {
static FontCollection* collection = nullptr;
static std::once_flag once;
std::call_once(once, [dirs]() { collection = new FontCollection(dirs); });
return *collection;
}
// TODO(garyq): Will be deprecated when full compatibility with Flutter Engine
// is complete.
FontCollection& FontCollection::GetDefaultFontCollection() {
return GetFontCollection("");
}
FontCollection::FontCollection() {
FontCollection("");
}
FontCollection::FontCollection(CacheMethod cache_method) {
FontCollection("", cache_method);
}
FontCollection::FontCollection(std::string dir, CacheMethod cache_method) {
std::vector<std::string> dirs = {dir};
FontCollection(std::move(dirs), cache_method);
}
FontCollection::FontCollection(const std::vector<std::string>& dirs,
CacheMethod cache_method) {
for (std::string dir : dirs) {
if (dir.length() != 0) {
AddFontMgr(dir, false);
}
}
skia_font_managers_.push_back(SkFontMgr::RefDefault());
DiscoverFamilyNames();
cache_method_ = cache_method;
}
FontCollection::~FontCollection() = default;
void FontCollection::AddFontMgr(std::string dir, bool rediscover_family_names) {
#ifdef DIRECTORY_FONT_MANAGER_AVAILABLE
// On Linux systems:
skia_font_managers_.push_back(SkFontMgr_New_Custom_Directory(dir.c_str()));
#endif
#ifdef ANDROID_FONT_MANAGER_AVAILABLE
// On Android:
SkFontMgr_Android_CustomFonts android_custom_font_data;
// Ensure the dir string is '/' terminated.
if (dir.back() != '/')
dir += '/';
android_custom_font_data.fBasePath = dir.data();
android_custom_font_data.fSystemFontUse =
SkFontMgr_Android_CustomFonts::SystemFontUse::kOnlyCustom;
skia_font_managers_.push_back(
SkFontMgr_New_Android(&android_custom_font_data));
#endif
if (rediscover_family_names)
DiscoverFamilyNames(skia_font_managers_.back());
}
void FontCollection::AddFontMgr(sk_sp<SkFontMgr> font_mgr,
bool rediscover_family_names) {
skia_font_managers_.push_back(font_mgr);
if (rediscover_family_names)
DiscoverFamilyNames(font_mgr);
}
void FontCollection::DiscoverFamilyNames() {
for (sk_sp<SkFontMgr> mgr : skia_font_managers_) {
DiscoverFamilyNames(mgr);
}
}
void FontCollection::DiscoverFamilyNames(sk_sp<SkFontMgr> mgr) {
SkString str;
for (int i = 0; i < mgr->countFamilies(); i++) {
mgr->getFamilyName(i, &str);
family_names_.insert(std::string{str.writable_str()});
}
}
std::set<std::string> FontCollection::GetFamilyNames() {
return family_names_;
}
bool FontCollection::HasFamily(const std::string family) const {
return family_names_.count(family) == 1;
}
void FontCollection::FlushCache() {
minikin_font_collection_map_.clear();
lru_tracker_.clear();
}
void FontCollection::SetCacheCapacity(const size_t cap) {
cache_capacity_ = cap;
}
void FontCollection::SetLowMemoryMode(bool mode, size_t cap) {
cache_capacity_ = cap;
if (mode) {
cache_method_ = CacheMethod::kLRU;
TrimCache();
} else {
cache_method_ = CacheMethod::kUnlimited;
}
}
void FontCollection::TrimCache() {
while (minikin_font_collection_map_.size() > cache_capacity_) {
std::string family_to_evict = lru_tracker_.back();
lru_tracker_.pop_back();
minikin_font_collection_map_.erase(family_to_evict);
}
}
const std::string FontCollection::ProcessFamilyName(const std::string& family) {
#ifdef DIRECTORY_FONT_MANAGER_AVAILABLE
return family.length() == 0 ? DEFAULT_FAMILY_NAME : family;
#else
if (family.length() > 0 &&
GetFamilyNames().count(family) > 0) { // Ensure family exists.
return family;
} else {
if (GetFamilyNames().count(DEFAULT_FAMILY_NAME) > 0) {
return DEFAULT_FAMILY_NAME;
}
return *GetFamilyNames().begin(); // First family available.
}
#endif
}
std::shared_ptr<minikin::FontCollection>
FontCollection::GetMinikinFontCollectionForFamily(const std::string& family) {
FTL_DCHECK(skia_font_managers_.size() > 0);
std::string processed_family_name = ProcessFamilyName(family);
// Only obtain new font family if the font has changed between runs.
if (cache_method_ == CacheMethod::kNone ||
minikin_font_collection_map_.count(processed_family_name) == 0) {
// Ask Skia to resolve a font style set for a font family name.
// FIXME(chinmaygarde): CoreText crashes when passed a null string. This
// seems to be a bug in Skia as SkFontMgr explicitly says passing in
// nullptr gives the default font.
for (sk_sp<SkFontMgr> mgr : skia_font_managers_) {
FTL_DCHECK(mgr != nullptr);
auto font_style_set = mgr->matchFamily(processed_family_name.c_str());
if (font_style_set != nullptr) {
std::vector<minikin::Font> minikin_fonts;
// Add fonts to the Minikin font family.
for (int i = 0, style_count = font_style_set->count(); i < style_count;
++i) {
// Create the skia typeface
auto skia_typeface =
sk_ref_sp<SkTypeface>(font_style_set->createTypeface(i));
if (skia_typeface == nullptr) {
continue;
}
// Create the minikin font from the skia typeface.
// Divide by 100 because the weights are given as "100", "200", etc.
minikin::Font minikin_font(
std::make_shared<FontSkia>(skia_typeface),
minikin::FontStyle{skia_typeface->fontStyle().weight() / 100,
skia_typeface->isItalic()});
minikin_fonts.emplace_back(std::move(minikin_font));
}
// Create a Minikin font family.
auto minikin_family =
std::make_shared<minikin::FontFamily>(std::move(minikin_fonts));
// Create a vector of font families for the Minkin font collection. For
// now, we only have one family in our collection.
std::vector<std::shared_ptr<minikin::FontFamily>> minikin_families = {
minikin_family,
};
// Assign the font collection.
minikin_font_collection_map_[processed_family_name] =
std::make_shared<minikin::FontCollection>(minikin_families);
return minikin_font_collection_map_[processed_family_name];
}
}
// Uh oh! Font family not found in any of the font managers!
minikin_font_collection_map_[processed_family_name] = nullptr;
}
// Maintain LRU and evict old fonts no longer used.
lru_tracker_.remove(processed_family_name);
lru_tracker_.push_front(processed_family_name);
if (cache_method_ == CacheMethod::kLRU) {
TrimCache();
}
return minikin_font_collection_map_[processed_family_name];
}
} // namespace txt

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_FONT_COLLECTION_H_
#define LIB_TXT_SRC_FONT_COLLECTION_H_
#define DEFAULT_FAMILY_NAME "Roboto"
#ifdef ANDROID_FONT_MANAGER_AVAILABLE
#undef DEFAULT_FAMILY_NAME
// On Android, Roboto is called 'sans-serif'
#define DEFAULT_FAMILY_NAME "sans-serif"
#endif
#define DEFAULT_CACHE_CAPACITY 20
#include <list>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "lib/ftl/macros.h"
#include "minikin/FontCollection.h"
#include "minikin/FontFamily.h"
#include "third_party/gtest/include/gtest/gtest_prod.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/ports/SkFontMgr.h"
namespace txt {
// FontCollection holds a vector of Skia Font Managers and handles font
// fallback. If no additional font directories are provided, then only the
// default font directory will be available.
class FontCollection {
public:
enum CacheMethod {
kNone,
kLRU, // Least Recently Used.
kUnlimited,
};
// TODO(garyq): Will be deprecated when full compatibility with Flutter Engine
// is complete.
static FontCollection& GetDefaultFontCollection();
// TODO(garyq): Will be deprecated when full compatibility with Flutter Engine
// is complete..
static FontCollection& GetFontCollection(std::string dir = "");
// TODO(garyq): Will be deprecated when full compatibility with Flutter Engine
// is complete.
static FontCollection& GetFontCollection(std::vector<std::string> dirs);
// Provides a pointer to the minikin FontCollection for the given font family.
// If the famly is not in any font manager, this will return a nullptr. Once a
// font is loaded, it is cached and future calls will be very efficient
// (until/if the font is flushed).
std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForFamily(
const std::string& family);
FontCollection(const std::vector<std::string>& dirs,
CacheMethod cache_method = CacheMethod::kUnlimited);
FontCollection(std::string dir,
CacheMethod cache_method = CacheMethod::kUnlimited);
FontCollection(CacheMethod cache_method);
FontCollection();
~FontCollection();
// Provides a set of all available family names.
std::set<std::string> GetFamilyNames();
// Returns true when the supplied font family exists in any of the font
// managers.
bool HasFamily(const std::string family) const;
// Adds a new SkFontMgr to the front of the stack of font managers.
void AddFontMgr(std::string dir, bool rediscover_family_names = true);
// Adds the provided SkFontMgr to the front of the stack of font managers.
void AddFontMgr(sk_sp<SkFontMgr> font_mgr,
bool rediscover_family_names = true);
// Removes all fonts that do not fit in the cache capacity from memory.
void FlushCache();
// When in LRU mode, the cache will only hold the <cap> most recently used
// fonts. This may be used when the application becomes low on memory or a
// very large number of fonts are used.
void SetCacheCapacity(const size_t cap);
// Call this to limit memory usage by cached fonts. SetLowMemoryMode() will
// enable default LRU policy and flush fonts beyond capacity.
void SetLowMemoryMode(bool mode = true, size_t cap = DEFAULT_CACHE_CAPACITY);
private:
std::vector<sk_sp<SkFontMgr>> skia_font_managers_;
// Cache the names because GetFamilyNames() can be frequently called.
std::set<std::string> family_names_;
CacheMethod cache_method_ = CacheMethod::kUnlimited;
std::list<std::string> lru_tracker_;
size_t cache_capacity_ = DEFAULT_CACHE_CAPACITY;
// Cache minikin font collections to prevent slow disk reads.
std::unordered_map<std::string, std::shared_ptr<minikin::FontCollection>>
minikin_font_collection_map_;
FRIEND_TEST(FontCollection, HasDefaultRegistrations);
FRIEND_TEST(FontCollection, GetMinikinFontCollections);
FRIEND_TEST(FontCollection, GetFamilyNames);
// Postprocess the family name to handle the following properties: fallback
// when not found and reverting to the default name when no fallback is found.
const std::string ProcessFamilyName(const std::string& family);
// Polls all of the SkFontMgrs to obtain a set of all available font family
// names.
void DiscoverFamilyNames();
// Add the family names of mgr to set of available font family names.
void DiscoverFamilyNames(sk_sp<SkFontMgr> mgr);
void TrimCache();
static const std::string GetDefaultFamilyName() {
return DEFAULT_FAMILY_NAME;
};
};
} // namespace txt
#endif // LIB_TXT_SRC_FONT_COLLECTION_H_

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#include "lib/txt/src/font_skia.h"
#include <minikin/MinikinFont.h>
namespace txt {
namespace {
hb_blob_t* GetTable(hb_face_t* face, hb_tag_t tag, void* context) {
SkTypeface* typeface = reinterpret_cast<SkTypeface*>(context);
const size_t table_size = typeface->getTableSize(tag);
if (table_size == 0)
return nullptr;
void* buffer = malloc(table_size);
if (buffer == nullptr)
return nullptr;
size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer);
if (table_size != actual_size) {
free(buffer);
return nullptr;
}
return hb_blob_create(reinterpret_cast<char*>(buffer), table_size,
HB_MEMORY_MODE_WRITABLE, buffer, free);
}
} // namespace
FontSkia::FontSkia(sk_sp<SkTypeface> typeface)
: MinikinFont(typeface->uniqueID()), typeface_(std::move(typeface)) {}
FontSkia::~FontSkia() = default;
static void FontSkia_SetSkiaPaint(sk_sp<SkTypeface> typeface,
SkPaint* skPaint,
const minikin::MinikinPaint& paint) {
skPaint->setTypeface(std::move(typeface));
skPaint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
// TODO: set more paint parameters from Minikin
skPaint->setTextSize(paint.size);
}
float FontSkia::GetHorizontalAdvance(uint32_t glyph_id,
const minikin::MinikinPaint& paint) const {
SkPaint skPaint;
uint16_t glyph16 = glyph_id;
SkScalar skWidth;
FontSkia_SetSkiaPaint(typeface_, &skPaint, paint);
skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL);
return skWidth;
}
void FontSkia::GetBounds(minikin::MinikinRect* bounds,
uint32_t glyph_id,
const minikin::MinikinPaint& paint) const {
SkPaint skPaint;
uint16_t glyph16 = glyph_id;
SkRect skBounds;
FontSkia_SetSkiaPaint(typeface_, &skPaint, paint);
skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds);
bounds->mLeft = skBounds.fLeft;
bounds->mTop = skBounds.fTop;
bounds->mRight = skBounds.fRight;
bounds->mBottom = skBounds.fBottom;
}
hb_face_t* FontSkia::CreateHarfBuzzFace() const {
return hb_face_create_for_tables(GetTable, typeface_.get(), 0);
}
const std::vector<minikin::FontVariation>& FontSkia::GetAxes() const {
return variations_;
}
const sk_sp<SkTypeface>& FontSkia::GetSkTypeface() const {
return typeface_;
}
} // namespace txt

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#include <SkPaint.h>
#include <SkTypeface.h>
#include <minikin/MinikinFont.h>
#include "lib/ftl/macros.h"
namespace txt {
class FontSkia : public minikin::MinikinFont {
public:
explicit FontSkia(sk_sp<SkTypeface> typeface);
~FontSkia();
float GetHorizontalAdvance(uint32_t glyph_id,
const minikin::MinikinPaint& paint) const override;
void GetBounds(minikin::MinikinRect* bounds,
uint32_t glyph_id,
const minikin::MinikinPaint& paint) const override;
hb_face_t* CreateHarfBuzzFace() const override;
const std::vector<minikin::FontVariation>& GetAxes() const override;
const sk_sp<SkTypeface>& GetSkTypeface() const;
private:
sk_sp<SkTypeface> typeface_;
std::vector<minikin::FontVariation> variations_;
FTL_DISALLOW_COPY_AND_ASSIGN(FontSkia);
};
} // namespace txt

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_FONT_STYLE_H_
#define LIB_TXT_SRC_FONT_STYLE_H_
namespace txt {
enum class FontStyle {
normal,
italic,
};
} // namespace txt
#endif // LIB_TXT_SRC_FONT_STYLE_H_

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_FONT_WEIGHT_H_
#define LIB_TXT_SRC_FONT_WEIGHT_H_
namespace txt {
enum class FontWeight {
w100, // Thin
w200, // Extra-Light
w300, // Light
w400, // Normal/Regular
w500, // Medium
w600, // Semi-bold
w700, // Bold
w800, // Extra-Bold
w900, // Black
};
} // namespace txt
#endif // LIB_TXT_SRC_FONT_WEIGHT_H_

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#include "lib/txt/src/paint_record.h"
#include "lib/ftl/logging.h"
namespace txt {
PaintRecord::~PaintRecord() = default;
PaintRecord::PaintRecord(TextStyle style,
SkPoint offset,
sk_sp<SkTextBlob> text,
SkPaint::FontMetrics metrics,
size_t line,
double run_width)
: style_(style),
offset_(offset),
text_(std::move(text)),
metrics_(metrics),
line_(line),
run_width_(run_width) {}
PaintRecord::PaintRecord(TextStyle style,
sk_sp<SkTextBlob> text,
SkPaint::FontMetrics metrics,
size_t line,
double run_width)
: style_(style),
text_(std::move(text)),
metrics_(metrics),
line_(line),
run_width_(run_width) {}
PaintRecord::PaintRecord(PaintRecord&& other) {
style_ = other.style_;
offset_ = other.offset_;
text_ = std::move(other.text_);
metrics_ = other.metrics_;
line_ = other.line_;
run_width_ = other.run_width_;
}
PaintRecord& PaintRecord::operator=(PaintRecord&& other) {
style_ = other.style_;
offset_ = other.offset_;
text_ = std::move(other.text_);
metrics_ = other.metrics_;
line_ = other.line_;
run_width_ = other.run_width_;
return *this;
}
void PaintRecord::SetOffset(SkPoint pt) {
offset_ = pt;
}
} // namespace txt

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_PAINT_RECORD_H_
#define LIB_TXT_SRC_PAINT_RECORD_H_
#include "lib/ftl/logging.h"
#include "lib/ftl/macros.h"
#include "lib/txt/src/text_style.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkTextBlob.h"
namespace txt {
// PaintRecord holds the layout data after Paragraph::Layout() is called. This
// stores all nessecary offsets, blobs, metrics, and more for Skia to draw the
// text.
class PaintRecord {
public:
PaintRecord() = delete;
~PaintRecord();
PaintRecord(TextStyle style,
SkPoint offset,
sk_sp<SkTextBlob> text,
SkPaint::FontMetrics metrics,
size_t line,
double run_width);
PaintRecord(TextStyle style,
sk_sp<SkTextBlob> text,
SkPaint::FontMetrics metrics,
size_t line,
double run_width);
PaintRecord(PaintRecord&& other);
PaintRecord& operator=(PaintRecord&& other);
SkPoint offset() const { return offset_; }
void SetOffset(SkPoint pt);
SkTextBlob* text() const { return text_.get(); }
const SkPaint::FontMetrics& metrics() const { return metrics_; }
const TextStyle& style() const { return style_; }
size_t line() const { return line_; }
size_t GetRunWidth() const { return run_width_; }
private:
TextStyle style_;
// offset_ is the overall offset of the origin of the SkTextBlob.
SkPoint offset_;
// SkTextBlob stores the glyphs and coordinates to draw them.
sk_sp<SkTextBlob> text_;
// FontMetrics stores the measurements of the font used.
SkPaint::FontMetrics metrics_;
size_t line_;
double run_width_ = 0.0f;
FTL_DISALLOW_COPY_AND_ASSIGN(PaintRecord);
};
} // namespace txt
#endif // LIB_TXT_SRC_PAINT_RECORD_H_

View File

@@ -0,0 +1,889 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#include "lib/txt/src/paragraph.h"
#include <hb.h>
#include <algorithm>
#include <limits>
#include <tuple>
#include <utility>
#include <vector>
#include <minikin/Layout.h>
#include "lib/ftl/logging.h"
#include "lib/txt/include/minikin/MinikinFont.h"
#include "lib/txt/libs/minikin/HbFontCache.h"
#include "lib/txt/libs/minikin/LayoutUtils.h"
#include "lib/txt/src/font_collection.h"
#include "lib/txt/src/font_skia.h"
#include "minikin/LineBreaker.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkTextBlob.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "third_party/skia/include/effects/SkDashPathEffect.h"
#include "third_party/skia/include/effects/SkDiscretePathEffect.h"
namespace txt {
namespace {
const sk_sp<SkTypeface>& GetTypefaceForGlyph(const minikin::Layout& layout,
size_t index) {
const FontSkia* font = static_cast<const FontSkia*>(layout.getFont(index));
return font->GetSkTypeface();
}
// Return the number of glyphs until the typeface changes.
size_t GetBlobLength(const minikin::Layout& layout, size_t blob_start) {
const size_t glyph_count = layout.nGlyphs();
const sk_sp<SkTypeface>& typeface = GetTypefaceForGlyph(layout, blob_start);
for (size_t blob_end = blob_start + 1; blob_end < glyph_count; ++blob_end) {
if (GetTypefaceForGlyph(layout, blob_end).get() != typeface.get())
return blob_end - blob_start;
}
return glyph_count - blob_start;
}
int GetWeight(const FontWeight weight) {
switch (weight) {
case FontWeight::w100:
return 1;
case FontWeight::w200:
return 2;
case FontWeight::w300:
return 3;
case FontWeight::w400: // Normal.
return 4;
case FontWeight::w500:
return 5;
case FontWeight::w600:
return 6;
case FontWeight::w700: // Bold.
return 7;
case FontWeight::w800:
return 8;
case FontWeight::w900:
return 9;
}
}
int GetWeight(const TextStyle& style) {
return GetWeight(style.font_weight);
}
bool GetItalic(const TextStyle& style) {
switch (style.font_style) {
case FontStyle::normal:
return false;
case FontStyle::italic:
return true;
}
}
void GetFontAndMinikinPaint(const TextStyle& style,
minikin::FontStyle* font,
minikin::MinikinPaint* paint) {
*font = minikin::FontStyle(GetWeight(style), GetItalic(style));
paint->size = style.font_size;
// Divide by font size so letter spacing is pixels, not proportional to font
// size.
paint->letterSpacing = style.letter_spacing / style.font_size;
paint->wordSpacing = style.word_spacing;
paint->scaleX = 1.0f;
// Prevent spacing rounding in Minikin. This causes jitter when switching
// between same text content with different runs composing it, however, it
// also produces more accurate layouts.
paint->paintFlags |= minikin::LinearTextFlag;
}
void GetPaint(const TextStyle& style, SkPaint* paint) {
paint->setTextSize(style.font_size);
}
} // namespace
static const float kDoubleDecorationSpacing = 3.0f;
Paragraph::Paragraph() = default;
Paragraph::~Paragraph() = default;
void Paragraph::SetText(std::vector<uint16_t> text, StyledRuns runs) {
needs_layout_ = true;
if (text.size() == 0)
return;
text_ = std::move(text);
runs_ = std::move(runs);
}
void Paragraph::InitBreaker() {
breaker_.setLocale(icu::Locale(), nullptr);
breaker_.resize(text_.size());
memcpy(breaker_.buffer(), text_.data(), text_.size() * sizeof(text_[0]));
breaker_.setText();
}
// NOTE: Minikin LineBreaker addStyleRun() has an O(N^2) (according to
// benchmarks) time complexity where N is the total number of characters.
// However, this is not significant for reasonably sized paragraphs. It is
// currently recommended to break up very long paragraphs (10k+ characters) to
// ensure speedy layout.
void Paragraph::AddRunsToLineBreaker(
std::unordered_map<std::string, std::shared_ptr<minikin::FontCollection>>&
collection_map) {
minikin::FontStyle font;
minikin::MinikinPaint paint;
for (size_t i = 0; i < runs_.size(); ++i) {
auto run = runs_.GetRun(i);
GetFontAndMinikinPaint(run.style, &font, &paint);
breaker_.addStyleRun(&paint,
font_collection_->GetMinikinFontCollectionForFamily(
run.style.font_family),
font, run.start, run.end, false,
run.style.letter_spacing);
}
}
void Paragraph::FillWhitespaceSet(size_t start,
size_t end,
hb_font_t* hb_font) {
uint32_t unusedGlyph;
for (size_t i = start; i < end; ++i) {
if (minikin::isWordSpace(text_[i])) {
hb_font_get_glyph(hb_font, text_[i], 0, &unusedGlyph);
whitespace_set_.insert(unusedGlyph);
}
}
}
void Paragraph::Layout(double width, bool force) {
// Do not allow calling layout multiple times without changing anything.
if (!needs_layout_ && width == width_ && !force)
return;
needs_layout_ = false;
width_ = width;
std::unordered_map<std::string, std::shared_ptr<minikin::FontCollection>>
collection_map;
breaker_.setLineWidths(0.0f, 0, width_);
// TODO(garyq): Get hyphenator working. Hyphenator should be created with
// a pattern binary dataset. Should be something along these lines:
//
// minikin::Hyphenator* hyph =
// minikin::Hyphenator::loadBinary(<paramsgohere>);
// breaker_.setLocale(icu::Locale::getRoot(), &hyph);
//
InitBreaker();
AddRunsToLineBreaker(collection_map);
breaker_.setJustified(paragraph_style_.text_align == TextAlign::justify);
breaker_.setStrategy(paragraph_style_.break_strategy);
size_t breaks_count = breaker_.computeBreaks();
const int* breaks = breaker_.getBreaks();
// Create a copy of text_ to use locally so that any changes made to the
// vector (such as removing newline characters) is not permanent.
std::vector<uint16_t> text(text_);
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
minikin::FontStyle font;
minikin::MinikinPaint minikin_paint;
minikin::Layout layout;
// Disable ligatures
// TODO(garyq): Re-enable ligatures.
minikin_paint.fontFeatureSettings += "-liga,-clig,";
SkTextBlobBuilder builder;
// Reset member variables so Layout still works when called more than once
lines_ = 0;
line_widths_ = std::vector<double>();
line_heights_ = std::vector<double>();
line_heights_.push_back(0);
records_ = std::vector<PaintRecord>();
// Set padding elements to have a minimum point.
glyph_position_x_ = std::vector<std::vector<double>>();
glyph_position_x_.push_back(std::vector<double>());
std::vector<double> glyph_single_line_position_x;
glyph_single_line_position_x.push_back(0);
// Track the x of the previous run to maintain accurate xposition when
// multiple SkTextBlobs make up a single line.
double previous_run_x_position = 0.0f;
SkScalar x = 0.0f;
SkScalar y = 0.0f;
size_t break_index = 0;
double max_line_spacing = 0.0f;
double max_descent = 0.0f;
double prev_max_descent = 0.0f;
double line_width = 0.0f;
std::vector<SkScalar> x_queue;
double justify_spacing = 0.0f;
double prev_word_pos = 0.0f;
double prev_char_advance = 0.0f;
double current_x_position = previous_run_x_position;
std::vector<const SkTextBlobBuilder::RunBuffer*> buffers;
std::vector<size_t> buffer_sizes;
int word_count = 0;
auto postprocess_line = [this, &x_queue, &y]() -> void {
size_t record_index = 0;
for (size_t i = 0; i < x_queue.size(); ++i) {
record_index = records_.size() - (x_queue.size() - i);
records_[record_index].SetOffset(SkPoint::Make(x_queue[i], y));
// Adjust the offsets for each of the different alignments.
switch (paragraph_style_.text_align) {
case TextAlign::left:
break;
case TextAlign::right: {
records_[record_index].SetOffset(SkPoint::Make(
records_[record_index].offset().x() + width_ -
breaker_.getWidths()[records_[record_index].line()],
records_[record_index].offset().y()));
break;
}
case TextAlign::center: {
records_[record_index].SetOffset(SkPoint::Make(
records_[record_index].offset().x() +
(width_ -
breaker_.getWidths()[records_[record_index].line()]) /
2,
records_[record_index].offset().y()));
break;
}
case TextAlign::justify: {
break;
}
}
}
// Correct positions stored in the member vars.
for (size_t y_index = 0; y_index < lines_; ++y_index) {
switch (paragraph_style_.text_align) {
case TextAlign::left:
break;
case TextAlign::right: {
for (size_t i = 0; i < glyph_position_x_[y_index].size(); ++i) {
glyph_position_x_[y_index][i] +=
width_ - breaker_.getWidths()[y_index];
}
break;
}
case TextAlign::center: {
for (size_t i = 0; i < glyph_position_x_[y_index].size(); ++i) {
glyph_position_x_[y_index][i] +=
(width_ - breaker_.getWidths()[y_index]) / 2;
}
break;
}
case TextAlign::justify: {
// TODO(garyq): Track position changes due to justify in justify
// method.
break;
}
}
}
x_queue.clear();
};
for (size_t run_index = 0; run_index < runs_.size(); ++run_index) {
auto run = runs_.GetRun(run_index);
bool is_newline = text_[run.start] == '\n' && run.end - run.start == 1;
// Replace '\n' with a null character so that a 'missing glyph' box is not
// drawn.
if (is_newline)
text[run.start] = '\0';
GetFontAndMinikinPaint(run.style, &font, &minikin_paint);
GetPaint(run.style, &paint);
size_t layout_start = run.start;
// Layout until the end of the run or too many lines.
while (layout_start < run.end && lines_ < paragraph_style_.max_lines) {
const size_t next_break = (break_index > breaks_count - 1)
? std::numeric_limits<size_t>::max()
: breaks[break_index];
const size_t layout_end = std::min(run.end, next_break);
bool bidiFlags = paragraph_style_.rtl;
// Minikin Layout doLayout() has an O(N^2) (according to
// benchmarks) time complexity where N is the total number of characters.
// However, this is not significant for reasonably sized paragraphs. It is
// currently recommended to break up very long paragraphs (10k+
// characters) to ensure speedy layout.
layout.doLayout(text.data() + layout_start, 0, layout_end - layout_start,
layout_end - layout_start, bidiFlags, font, minikin_paint,
font_collection_->GetMinikinFontCollectionForFamily(
run.style.font_family));
FillWhitespaceSet(layout_start, layout_end,
minikin::getHbFontLocked(layout.getFont(0)));
const size_t glyph_count = layout.nGlyphs();
size_t blob_start = 0;
// Each blob.
buffers = std::vector<const SkTextBlobBuilder::RunBuffer*>();
buffer_sizes = std::vector<size_t>();
word_count = 0;
double temp_line_spacing = 0;
current_x_position = 0;
while (blob_start < glyph_count) {
const size_t blob_length = GetBlobLength(layout, blob_start);
buffer_sizes.push_back(blob_length);
// TODO(abarth): Precompute when we can use allocRunPosH.
paint.setTypeface(GetTypefaceForGlyph(layout, blob_start));
// Check if we should remove trailing whitespace of blobs.
size_t trailing_length = 0;
while ((paragraph_style_.text_align == TextAlign::center ||
paragraph_style_.text_align == TextAlign::right) &&
whitespace_set_.count(layout.getGlyphId(
blob_start + blob_length - trailing_length - 1)) > 0 &&
layout_end == next_break) {
++trailing_length;
}
buffers.push_back(
&builder.allocRunPos(paint, blob_length - trailing_length));
// TODO(garyq): Implement RTL.
// Each Glyph/Letter.
bool whitespace_ended = true;
float letter_spacing = 0;
for (size_t blob_index = 0; blob_index < blob_length - trailing_length;
++blob_index) {
const size_t glyph_index = blob_start + blob_index;
buffers.back()->glyphs[blob_index] = layout.getGlyphId(glyph_index);
const size_t pos_index = 2 * blob_index;
// Extract the letter spacing by itself out of the minikin layout.
letter_spacing = run.style.letter_spacing == 0
? 0
: layout.getX(glyph_index) - current_x_position;
buffers.back()->pos[pos_index] = current_x_position + letter_spacing;
glyph_single_line_position_x.push_back(
current_x_position + previous_run_x_position + letter_spacing);
buffers.back()->pos[pos_index + 1] = layout.getY(glyph_index);
current_x_position += layout.getCharAdvance(glyph_index);
// Check if the current Glyph is a whitespace and handle multiple
// whitespaces in a row.
if (whitespace_set_.count(layout.getGlyphId(glyph_index)) > 0) {
// Only increment word_count if it is the first in a series of
// whitespaces.
if (whitespace_ended) {
++word_count;
}
whitespace_ended = false;
} else {
whitespace_ended = true;
}
prev_char_advance = layout.getCharAdvance(glyph_index);
}
blob_start += blob_length;
previous_run_x_position += current_x_position + letter_spacing;
}
// TODO(abarth): We could keep the same SkTextBlobBuilder as long as the
// color stayed the same.
SkPaint::FontMetrics metrics;
paint.getFontMetrics(&metrics);
// Apply additional word spacing if the text is justified.
if (paragraph_style_.text_align == TextAlign::justify &&
buffer_sizes.size() > 0) {
JustifyLine(buffers, buffer_sizes, word_count, justify_spacing);
}
records_.push_back(PaintRecord{run.style, builder.make(), metrics, lines_,
layout.getAdvance()});
line_width +=
std::abs(records_[records_.size() - 1].text()->bounds().fRight +
records_[records_.size() - 1].text()->bounds().fLeft);
// Must adjust each line to the largest text in the line, so cannot
// directly push the offset property of PaintRecord until line is
// finished.
x_queue.push_back(x);
temp_line_spacing = lines_ == 0 ? -metrics.fAscent * run.style.height
: (-metrics.fAscent + metrics.fLeading) *
run.style.height;
if (max_line_spacing < temp_line_spacing) {
max_line_spacing = temp_line_spacing;
// Record the alphabetic_baseline_ and idegraphic_baseline_:
if (lines_ == 0) {
alphabetic_baseline_ = -metrics.fAscent * run.style.height;
// TODO(garyq): Properly implement ideographic_baseline_.
ideographic_baseline_ =
(metrics.fUnderlinePosition - metrics.fAscent) * run.style.height;
}
}
temp_line_spacing = metrics.fDescent * run.style.height;
if (max_descent < temp_line_spacing)
max_descent = temp_line_spacing;
if (layout_end == next_break || is_newline) {
y += roundf(max_line_spacing + prev_max_descent);
line_heights_.push_back(
(line_heights_.empty() ? 0 : line_heights_.back()) +
roundf(max_line_spacing + max_descent));
glyph_single_line_position_x.push_back(
glyph_single_line_position_x.back() + prev_char_advance);
glyph_single_line_position_x.push_back(FLT_MAX);
glyph_position_x_.push_back(glyph_single_line_position_x);
prev_max_descent = max_descent;
line_widths_.push_back(line_width);
postprocess_line();
// Reset Variables for next line.
max_line_spacing = 0.0f;
max_descent = 0.0f;
x = 0.0f;
prev_word_pos = 0;
prev_char_advance = 0.0f;
previous_run_x_position = 0.0f;
current_x_position = 0.0f;
line_width = 0.0f;
break_index += 1;
lines_++;
glyph_single_line_position_x = std::vector<double>();
glyph_single_line_position_x.push_back(0);
} else {
x += layout.getAdvance();
}
layout_start = layout_end;
}
}
// Handle last line tasks.
y += roundf(max_line_spacing + prev_max_descent);
postprocess_line();
if (line_width != 0)
line_widths_.push_back(line_width);
// Finalize measurements
line_heights_.push_back((line_heights_.empty() ? 0 : line_heights_.back()) +
roundf(max_line_spacing + max_descent));
glyph_single_line_position_x.push_back(glyph_single_line_position_x.back() +
prev_char_advance);
glyph_single_line_position_x.push_back(FLT_MAX);
glyph_position_x_.push_back(glyph_single_line_position_x);
// Remove justification on the last line.
if (paragraph_style_.text_align == TextAlign::justify &&
buffer_sizes.size() > 0) {
JustifyLine(buffers, buffer_sizes, word_count, justify_spacing, -1);
}
line_widths_ =
std::vector<double>(breaker_.getWidths(), breaker_.getWidths() + lines_);
CalculateIntrinsicWidths();
breaker_.finish();
}
// Amends the buffers to incorporate justification.
void Paragraph::JustifyLine(
std::vector<const SkTextBlobBuilder::RunBuffer*>& buffers,
std::vector<size_t>& buffer_sizes,
int word_count,
double& justify_spacing,
double multiplier) {
// We will use the previous justification spacing when undoing justification.
if (multiplier > 0) {
justify_spacing =
(width_ - breaker_.getWidths()[lines_]) / (word_count - 1);
}
word_count = 0;
bool whitespace_ended = true;
for (size_t i = 0; i < buffers.size(); ++i) {
for (size_t glyph_index = 0; glyph_index < buffer_sizes[i]; ++glyph_index) {
// Check if the current Glyph is a whitespace and handle multiple
// whitespaces in a row.
if (whitespace_set_.count(buffers[i]->glyphs[glyph_index]) > 0) {
// Only increment word_count and add justification spacing to
// whitespace if it is the first in a series of whitespaces.
if (whitespace_ended) {
++word_count;
buffers[i]->pos[glyph_index * 2] +=
justify_spacing * multiplier * word_count;
}
whitespace_ended = false;
} else {
// Add justification spacing for all non-whitespace glyphs.
buffers[i]->pos[glyph_index * 2] +=
justify_spacing * multiplier * word_count;
whitespace_ended = true;
}
}
}
}
const ParagraphStyle& Paragraph::GetParagraphStyle() const {
return paragraph_style_;
}
double Paragraph::GetAlphabeticBaseline() const {
// Currently -fAscent
return alphabetic_baseline_;
}
double Paragraph::GetIdeographicBaseline() const {
// TODO(garyq): Currently -fAscent + fUnderlinePosition. Verify this.
return ideographic_baseline_;
}
void Paragraph::CalculateIntrinsicWidths() {
max_intrinsic_width_ = 0;
for (size_t i = 0; i < line_widths_.size(); ++i) {
max_intrinsic_width_ += line_widths_[i];
}
// TODO(garyq): Investigate correctness of the following implementation of min
// intrinsic width. This is currently the longest line in the text after
// layout.
min_intrinsic_width_ = 0;
for (size_t i = 0; i < line_widths_.size(); ++i) {
min_intrinsic_width_ = std::max(min_intrinsic_width_, line_widths_[i]);
}
// Ensure that min < max widths.
min_intrinsic_width_ = std::min(max_intrinsic_width_, min_intrinsic_width_);
max_intrinsic_width_ = std::max(max_intrinsic_width_, min_intrinsic_width_);
}
double Paragraph::GetMaxIntrinsicWidth() const {
return max_intrinsic_width_;
}
double Paragraph::GetMinIntrinsicWidth() const {
return min_intrinsic_width_;
}
size_t Paragraph::TextSize() const {
return text_.size();
}
double Paragraph::GetHeight() const {
return line_heights_[line_heights_.size() - 2];
}
double Paragraph::GetLayoutWidth() const {
double w = 0;
for (size_t i = 0; i < line_widths_.size(); ++i) {
w = std::max(w, line_widths_[i]);
}
return w;
}
double Paragraph::GetMaxWidth() const {
return width_;
}
void Paragraph::SetParagraphStyle(const ParagraphStyle& style) {
needs_layout_ = true;
paragraph_style_ = style;
}
void Paragraph::SetFontCollection(FontCollection* font_collection) {
font_collection_ = font_collection;
}
// The x,y coordinates will be the very top left corner of the rendered
// paragraph.
void Paragraph::Paint(SkCanvas* canvas, double x, double y) {
SkAutoCanvasRestore canvas_restore(canvas, true);
canvas->translate(x, y);
SkPaint paint;
for (size_t index = 0; index < records_.size(); ++index) {
PaintRecord& record = records_[index];
paint.setColor(record.style().color);
SkPoint offset = record.offset();
canvas->drawTextBlob(record.text(), offset.x(), offset.y(), paint);
PaintDecorations(canvas, offset.x(), offset.y(), index);
}
}
void Paragraph::PaintDecorations(SkCanvas* canvas,
double x,
double y,
size_t record_index) {
PaintRecord& record = records_[record_index];
if (record.style().decoration == TextDecoration::kNone)
return;
const SkPaint::FontMetrics& metrics = record.metrics();
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
if (record.style().decoration_color == SK_ColorTRANSPARENT) {
paint.setColor(record.style().color);
} else {
paint.setColor(record.style().decoration_color);
}
paint.setAntiAlias(true);
// This is set to 2 for the double line style
int decoration_count = 1;
// Filled when drawing wavy decorations.
SkPath path;
double width = 0;
if (paragraph_style_.text_align == TextAlign::justify &&
record.line() != lines_ - 1) {
width = width_;
} else {
width = record.GetRunWidth();
}
paint.setStrokeWidth(
(SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags::
kUnderlineThicknessIsValid_Flag))
? metrics.fUnderlineThickness *
record.style().decoration_thickness_multiplier
// Backup value if the fUnderlineThickness metric is not available:
// Divide by 14pt as it is the default size.
: record.style().font_size / 14.0f *
record.style().decoration_thickness_multiplier);
// Setup the decorations.
switch (record.style().decoration_style) {
case TextDecorationStyle::kSolid: {
break;
}
case TextDecorationStyle::kDouble: {
decoration_count = 2;
break;
}
// Note: the intervals are scaled by the thickness of the line, so it is
// possible to change spacing by changing the decoration_thickness
// property of TextStyle.
case TextDecorationStyle::kDotted: {
// Divide by 14pt as it is the default size.
const float scale = record.style().font_size / 14.0f;
const SkScalar intervals[] = {1.0f * scale, 1.5f * scale, 1.0f * scale,
1.5f * scale};
size_t count = sizeof(intervals) / sizeof(intervals[0]);
paint.setPathEffect(SkPathEffect::MakeCompose(
SkDashPathEffect::Make(intervals, count, 0.0f),
SkDiscretePathEffect::Make(0, 0)));
break;
}
// Note: the intervals are scaled by the thickness of the line, so it is
// possible to change spacing by changing the decoration_thickness
// property of TextStyle.
case TextDecorationStyle::kDashed: {
// Divide by 14pt as it is the default size.
const float scale = record.style().font_size / 14.0f;
const SkScalar intervals[] = {4.0f * scale, 2.0f * scale, 4.0f * scale,
2.0f * scale};
size_t count = sizeof(intervals) / sizeof(intervals[0]);
paint.setPathEffect(SkPathEffect::MakeCompose(
SkDashPathEffect::Make(intervals, count, 0.0f),
SkDiscretePathEffect::Make(0, 0)));
break;
}
case TextDecorationStyle::kWavy: {
int wave_count = 0;
double x_start = 0;
double wavelength = metrics.fUnderlineThickness *
record.style().decoration_thickness_multiplier;
path.moveTo(x, y);
while (x_start + wavelength * 2 < width) {
path.rQuadTo(wavelength, wave_count % 2 != 0 ? wavelength : -wavelength,
wavelength * 2, 0);
x_start += wavelength * 2;
++wave_count;
}
break;
}
}
// Draw the decorations.
// Use a for loop for "kDouble" decoration style
for (int i = 0; i < decoration_count; i++) {
double y_offset =
i * metrics.fUnderlineThickness * kDoubleDecorationSpacing;
double y_offset_original = y_offset;
// Underline
if (record.style().decoration & 0x1) {
y_offset +=
(SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags::
kUnderlinePositionIsValid_Flag))
? metrics.fUnderlinePosition
: metrics.fUnderlineThickness;
if (record.style().decoration_style != TextDecorationStyle::kWavy) {
canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint);
} else {
SkPath offsetPath = path;
offsetPath.offset(0, y_offset);
canvas->drawPath(offsetPath, paint);
}
y_offset = y_offset_original;
}
// Overline
if (record.style().decoration & 0x2) {
// We subtract fAscent here because for double overlines, we want the
// second line to be above, not below the first.
y_offset -= metrics.fAscent;
if (record.style().decoration_style != TextDecorationStyle::kWavy) {
canvas->drawLine(x, y - y_offset, x + width, y - y_offset, paint);
} else {
SkPath offsetPath = path;
offsetPath.offset(0, -y_offset);
canvas->drawPath(offsetPath, paint);
}
y_offset = y_offset_original;
}
// Strikethrough
if (record.style().decoration & 0x4) {
if (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags::
kStrikeoutThicknessIsValid_Flag))
paint.setStrokeWidth(metrics.fStrikeoutThickness *
record.style().decoration_thickness_multiplier);
// Make sure the double line is "centered" vertically.
y_offset += (decoration_count - 1.0) * metrics.fUnderlineThickness *
kDoubleDecorationSpacing / -2.0;
y_offset +=
(SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags::
kStrikeoutThicknessIsValid_Flag))
? metrics.fStrikeoutPosition
// Backup value if the strikeoutposition metric is not
// available:
: metrics.fXHeight / -2.0;
if (record.style().decoration_style != TextDecorationStyle::kWavy) {
canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint);
} else {
SkPath offsetPath = path;
offsetPath.offset(0, y_offset);
canvas->drawPath(offsetPath, paint);
}
y_offset = y_offset_original;
}
}
}
std::vector<SkRect> Paragraph::GetRectsForRange(size_t start,
size_t end) const {
std::vector<SkRect> rects;
end = fmax(start, end);
start = fmin(start, end);
FTL_DCHECK(end >= start && end >= 0 && start >= 0);
if (end == start)
end = start + 1;
end = fmin(end, text_.size());
while (start < end) {
SkIPoint word_bounds = GetWordBoundary(start);
word_bounds.fX = fmax(start, word_bounds.fX);
word_bounds.fY = fmin(end, word_bounds.fY);
start = fmax(word_bounds.fY, start + 1);
SkRect left_limits = GetCoordinatesForGlyphPosition(word_bounds.fX);
SkRect right_limits = GetCoordinatesForGlyphPosition(word_bounds.fY - 1);
if (left_limits.top() < right_limits.top()) {
rects.push_back(SkRect::MakeLTRB(
0, right_limits.top(), right_limits.right(), right_limits.bottom()));
} else {
rects.push_back(SkRect::MakeLTRB(left_limits.left(), left_limits.top(),
right_limits.right(),
right_limits.bottom()));
}
}
return rects;
}
SkRect Paragraph::GetCoordinatesForGlyphPosition(size_t pos) const {
size_t remainder = fmin(pos, text_.size());
remainder++;
size_t line = 1;
for (line = 1; line < line_heights_.size() - 1; ++line) {
if (remainder > glyph_position_x_[line].size() - 3) {
remainder -= glyph_position_x_[line].size() - 3;
} else {
break;
}
}
return SkRect::MakeLTRB(glyph_position_x_[line][remainder],
line_heights_[line - 1],
remainder < glyph_position_x_[line].size() - 2
? glyph_position_x_[line][remainder + 1]
: line_widths_[line - 1],
line_heights_[line]);
}
size_t Paragraph::GetGlyphPositionAtCoordinate(
double dx,
double dy,
bool using_glyph_center_as_boundary) const {
size_t offset = 0;
size_t y_index = 1;
size_t prev_count = 0;
for (y_index = 1; y_index < line_heights_.size() - 2; ++y_index) {
if (dy < line_heights_[y_index]) {
offset += prev_count;
prev_count = glyph_position_x_[y_index - 1].size() - 3;
break;
} else {
offset += prev_count;
prev_count = glyph_position_x_[y_index].size() - 3;
}
}
if (y_index == line_heights_.size() - 2)
offset += prev_count;
prev_count = 0;
for (size_t x_index = 1; x_index < glyph_position_x_[y_index].size() - 1;
++x_index) {
if (dx < glyph_position_x_[y_index][x_index] -
(using_glyph_center_as_boundary
? (glyph_position_x_[y_index][x_index] -
glyph_position_x_[y_index][x_index - 1]) /
2.0f
: 0)) {
break;
} else {
offset += prev_count;
prev_count = 1;
}
}
return offset;
}
SkIPoint Paragraph::GetWordBoundary(size_t offset) const {
// TODO(garyq): Consider punctuation as separate words.
if (text_.size() == 0)
return SkIPoint::Make(0, 0);
return SkIPoint::Make(
minikin::getPrevWordBreakForCache(text_.data(), offset + 1, text_.size()),
minikin::getNextWordBreakForCache(text_.data(), offset, text_.size()));
}
int Paragraph::GetLineCount() const {
return lines_;
}
bool Paragraph::DidExceedMaxLines() const {
if (lines_ > paragraph_style_.max_lines)
return true;
return false;
}
void Paragraph::SetDirty(bool dirty) {
needs_layout_ = dirty;
}
} // namespace txt

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_PARAGRAPH_H_
#define LIB_TXT_SRC_PARAGRAPH_H_
#include <tuple>
#include <vector>
#include "lib/ftl/macros.h"
#include "lib/txt/src/font_collection.h"
#include "lib/txt/src/paint_record.h"
#include "lib/txt/src/paragraph_style.h"
#include "lib/txt/src/styled_runs.h"
#include "minikin/LineBreaker.h"
#include "third_party/gtest/include/gtest/gtest_prod.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkTextBlob.h"
class SkCanvas;
namespace txt {
using GlyphID = uint32_t;
// Paragraph provides Layout, metrics, and painting capabilites for text. Once a
// Paragraph is constructed with ParagraphBuilder::Build(), an example basic
// workflow can be this:
//
// std::unique_ptr<Paragraph> paragraph = paragraph_builder.Build();
// paragraph->Layout(<somewidthgoeshere>);
// paragraph->Paint(<someSkCanvas>, <xpos>, <ypos>);
class Paragraph {
public:
// Constructor. I is highly recommended to construct a paragrph with a
// ParagraphBuilder.
Paragraph();
~Paragraph();
// Minikin Layout doLayout() and LineBreaker addStyleRun() has an
// O(N^2) (according to benchmarks) time complexity where N is the total
// number of characters. However, this is not significant for reasonably sized
// paragraphs. It is currently recommended to break up very long paragraphs
// (10k+ characters) to ensure speedy layout.
//
// Layout calculates the positioning of all the glyphs. Must call this method
// before Painting and getting any statistics from this class.
void Layout(double width, bool force = false);
// Paints the Laid out text onto the supplied SkCanvas at (x, y) offset from
// the origin. Only valid after Layout() is called.
void Paint(SkCanvas* canvas, double x, double y);
// Getter for paragraph_style_.
const ParagraphStyle& GetParagraphStyle() const;
// Returns the number of characters/unicode characters. AKA text_.size()
size_t TextSize() const;
// Returns the height of the laid out paragraph. NOTE this is not a tight
// bounding height of the glyphs, as some glyphs do not reach as low as they
// can.
double GetHeight() const;
// Returns the actual max width of the longest line after Layout().
double GetLayoutWidth() const;
// Returns the width provided in the Layout() method. This is the maximum
// width any line in the laid out paragraph can occupy. We expect that
// GetMaxWidth() >= GetLayoutWidth().
double GetMaxWidth() const;
// Distance from top of paragraph to the Alphabetic baseline of the first
// line. Used for alphabetic fonts (A-Z, a-z, greek, etc.)
double GetAlphabeticBaseline() const;
// Distance from top of paragraph to the Ideographic baseline of the first
// line. Used for ideographic fonts (Chinese, Japanese, Korean, etc.)
double GetIdeographicBaseline() const;
// Returns the total width covered by the paragraph without linebreaking.
double GetMaxIntrinsicWidth() const;
// Currently, calculated similarly to as GetLayoutWidth(), however this is not
// nessecarily 100% correct in all cases.
//
// Returns the actual max width of the longest line after Layout().
double GetMinIntrinsicWidth() const;
// Returns a vector of bounding boxes that enclose all text between start and
// end glyph indexes, including start and excluding end.
std::vector<SkRect> GetRectsForRange(size_t start, size_t end) const;
// Returns the index of the glyph that corresponds to the provided coordinate,
// with the top left corner as the origin, and +y direction as down.
//
// When using_glyph_center_as_boundary == true, coords to the + direction of
// the center x-position of the glyph will be considered as the next glyph. A
// typical use-case for this is when the cursor is meant to be on either side
// of any given character. This allows the transition border to be middle of
// each character.
size_t GetGlyphPositionAtCoordinate(
double dx,
double dy,
bool using_glyph_center_as_boundary = false) const;
// Returns a bounding box that encloses the glyph at the index pos.
SkRect GetCoordinatesForGlyphPosition(size_t pos) const;
// Finds the first and last glyphs that define a word containing the glyph at
// index offset.
SkIPoint GetWordBoundary(size_t offset) const;
// Returns the number of lines the paragraph takes up. If the text exceeds the
// amount width and maxlines provides, Layout() truncates the extra text from
// the layout and this will return the max lines allowed.
int GetLineCount() const;
// Checks if the layout extends past the maximum lines and had to be
// truncated.
bool DidExceedMaxLines() const;
// Sets the needs_layout_ to dirty. When Layout() is called, a new Layout will
// be performed when this is set to true. Can also be used to prevent a new
// Layout from being calculated by setting to false.
void SetDirty(bool dirty = true);
private:
friend class ParagraphBuilder;
FRIEND_TEST(RenderTest, SimpleParagraph);
FRIEND_TEST(RenderTest, SimpleRedParagraph);
FRIEND_TEST(RenderTest, RainbowParagraph);
FRIEND_TEST(RenderTest, DefaultStyleParagraph);
FRIEND_TEST(RenderTest, BoldParagraph);
FRIEND_TEST(RenderTest, LeftAlignParagraph);
FRIEND_TEST(RenderTest, RightAlignParagraph);
FRIEND_TEST(RenderTest, CenterAlignParagraph);
FRIEND_TEST(RenderTest, JustifyAlignParagraph);
FRIEND_TEST(RenderTest, DecorationsParagraph);
FRIEND_TEST(RenderTest, ItalicsParagraph);
FRIEND_TEST(RenderTest, ChineseParagraph);
FRIEND_TEST(RenderTest, DISABLED_ArabicParagraph);
FRIEND_TEST(RenderTest, SpacingParagraph);
FRIEND_TEST(RenderTest, LongWordParagraph);
FRIEND_TEST(RenderTest, KernScaleParagraph);
FRIEND_TEST(RenderTest, NewlineParagraph);
FRIEND_TEST(RenderTest, EmojiParagraph);
FRIEND_TEST(RenderTest, HyphenBreakParagraph);
FRIEND_TEST(RenderTest, RepeatLayoutParagraph);
// Starting data to layout.
std::vector<uint16_t> text_;
StyledRuns runs_;
ParagraphStyle paragraph_style_;
FontCollection* font_collection_;
minikin::LineBreaker breaker_;
// Stores the result of Layout().
std::vector<PaintRecord> records_;
// TODO(garyq): Can we access this info without redundantly storing it here?
std::vector<double> line_widths_;
std::vector<double> line_heights_;
// Holds the laid out x positions of each glyph, as well as padding to make
// math on it simpler.
std::vector<std::vector<double>> glyph_position_x_;
// Set of glyph IDs that correspond to whitespace.
std::set<GlyphID> whitespace_set_;
// The max width of the paragraph as provided in the most recent Layout()
// call.
double width_ = -1.0f;
size_t lines_ = 0;
double max_intrinsic_width_ = 0;
double min_intrinsic_width_ = 0;
double alphabetic_baseline_ = FLT_MAX;
double ideographic_baseline_ = FLT_MAX;
bool needs_layout_ = true;
struct WaveCoordinates {
double x_start;
double y_start;
double x_end;
double y_end;
WaveCoordinates(double x_s, double y_s, double x_e, double y_e)
: x_start(x_s), y_start(y_s), x_end(x_e), y_end(y_e) {}
};
// Passes in the text and Styled Runs. text_ and runs_ will later be passed
// into breaker_ in InitBreaker(), which is called in Layout().
void SetText(std::vector<uint16_t> text, StyledRuns runs);
// Sets up breaker_ with the contents of text_ and runs_. This is called every
// Layout() call to allow for different widths to be used.
void InitBreaker();
void SetParagraphStyle(const ParagraphStyle& style);
void SetFontCollection(FontCollection* font_collection);
// Pass the runs to breaker_.
// NOTE: This is O(N^2) due to minikin breaking being O(N^2) where N = sum of
// all text added using this method. This is insignificant with normal usage.
void AddRunsToLineBreaker(
std::unordered_map<std::string, std::shared_ptr<minikin::FontCollection>>&
collection_map);
// Calculates the GlyphIDs of all whitespace characters present in the text
// between start and end. THis is used to correctly add extra whitespace when
// justifying.
void FillWhitespaceSet(size_t start, size_t end, hb_font_t* hb_font);
// Calculates and amends the layout for one line to be justified.
void JustifyLine(std::vector<const SkTextBlobBuilder::RunBuffer*>& buffers,
std::vector<size_t>& buffer_sizes,
int word_count,
double& justify_spacing,
double multiplier = 1);
// Creates and draws the decorations onto the canvas.
void PaintDecorations(SkCanvas* canvas,
double x,
double y,
size_t record_index);
void CalculateIntrinsicWidths();
FTL_DISALLOW_COPY_AND_ASSIGN(Paragraph);
};
} // namespace txt
#endif // LIB_TXT_SRC_PARAGRAPH_H_

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#include "lib/ftl/logging.h"
#include <list>
#include "lib/txt/src/paragraph_builder.h"
#include "lib/txt/src/paragraph_style.h"
#include "third_party/icu/source/common/unicode/unistr.h"
namespace txt {
ParagraphBuilder::ParagraphBuilder(ParagraphStyle style,
FontCollection* font_collection)
: font_collection_(font_collection) {
SetParagraphStyle(style);
}
ParagraphBuilder::ParagraphBuilder(ParagraphStyle style) {
SetParagraphStyle(style);
}
ParagraphBuilder::ParagraphBuilder() {}
void ParagraphBuilder::SetParagraphStyle(const ParagraphStyle& style) {
paragraph_style_ = style;
// Keep a default style to fall back to.
TextStyle text_style;
text_style.font_weight = paragraph_style_.font_weight;
text_style.font_style = paragraph_style_.font_style;
text_style.font_family = paragraph_style_.font_family;
text_style.font_size = paragraph_style_.font_size;
PushStyle(text_style);
}
void ParagraphBuilder::SetFontCollection(FontCollection* font_collection) {
font_collection_ = font_collection;
}
ParagraphBuilder::~ParagraphBuilder() = default;
void ParagraphBuilder::PushStyle(const TextStyle& style) {
const size_t text_index = text_.size();
runs_.EndRunIfNeeded(text_index);
const size_t style_index = runs_.AddStyle(style);
runs_.StartRun(style_index, text_index);
style_stack_.push_back(style_index);
}
void ParagraphBuilder::Pop() {
if (style_stack_.empty())
return;
const size_t text_index = text_.size();
runs_.EndRunIfNeeded(text_index);
style_stack_.pop_back();
if (style_stack_.empty())
return;
const size_t style_index = style_stack_.back();
runs_.StartRun(style_index, text_index);
}
const TextStyle& ParagraphBuilder::PeekStyle() const {
return runs_.PeekStyle();
}
void ParagraphBuilder::AddText(const std::u16string& text) {
text_.insert(text_.end(), text.begin(), text.end());
}
void ParagraphBuilder::AddText(const std::string& text) {
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
AddText(u16_text);
}
void ParagraphBuilder::AddText(const char* text) {
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
AddText(u16_text);
}
void ParagraphBuilder::SplitNewlineRuns() {
std::list<size_t> newline_positions;
for (size_t i = 0; i < text_.size(); ++i) {
if (text_[i] == '\n') {
newline_positions.push_back(i);
}
}
if (newline_positions.size() > 0)
runs_.SplitNewlineRuns(newline_positions);
}
std::unique_ptr<Paragraph> ParagraphBuilder::Build() {
if (font_collection_ == nullptr) {
// Will be deprecated when full compatibility with Flutter Engine is
// complete.
font_collection_ = &FontCollection::GetFontCollection("");
}
runs_.EndRunIfNeeded(text_.size());
SplitNewlineRuns();
std::unique_ptr<Paragraph> paragraph = std::make_unique<Paragraph>();
paragraph->SetText(std::move(text_), std::move(runs_));
paragraph->SetParagraphStyle(paragraph_style_);
paragraph->SetFontCollection(font_collection_);
return paragraph;
}
} // namespace txt

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_PARAGRAPH_BUILDER_H_
#define LIB_TXT_SRC_PARAGRAPH_BUILDER_H_
#include <memory>
#include <string>
#include "lib/ftl/macros.h"
#include "lib/txt/src/font_collection.h"
#include "lib/txt/src/paragraph.h"
#include "lib/txt/src/paragraph_style.h"
#include "lib/txt/src/styled_runs.h"
#include "lib/txt/src/text_style.h"
namespace txt {
class ParagraphBuilder {
public:
explicit ParagraphBuilder(ParagraphStyle style);
ParagraphBuilder(ParagraphStyle style, FontCollection* font_collection);
ParagraphBuilder();
~ParagraphBuilder();
// Push a style to the stack. The corresponding text added with AddText will
// use the top-most style.
void PushStyle(const TextStyle& style);
// Remove a style from the stack. Useful to apply different styles to chunks
// of text such as bolding.
// Example:
// builder.PushStyle(normal_style);
// builder.AddText("Hello this is normal. ");
//
// builder.PushStyle(bold_style);
// builder.AddText("And this is BOLD. ");
//
// builder.Pop();
// builder.AddText(" Back to normal again.");
void Pop();
// Returns the last TextStyle on the stack.
const TextStyle& PeekStyle() const;
// Adds text to the builder. Forms the proper runs to use the upper-most style
// on the style_stack_;
void AddText(const std::u16string& text);
// Converts to u16string before adding.
void AddText(const std::string& text);
// Converts to u16string before adding.
void AddText(const char* text);
void SetParagraphStyle(const ParagraphStyle& style);
// It is recommended to initialize the ParagraphBuilder with a font collection
// as default font collection fallback will be deprecated.
void SetFontCollection(FontCollection* font_collection);
// Constructs a Paragraph object that can be used to layout and paint the text
// to a SkCanvas.
std::unique_ptr<Paragraph> Build();
private:
std::vector<uint16_t> text_;
std::vector<size_t> style_stack_;
StyledRuns runs_;
ParagraphStyle paragraph_style_;
FontCollection* font_collection_ = nullptr;
// Break any newline '\n' characters into their own runs. This allows
// Paragraph::Layout to cleanly discover and handle newlines.
void SplitNewlineRuns();
FTL_DISALLOW_COPY_AND_ASSIGN(ParagraphBuilder);
};
} // namespace txt
#endif // LIB_TXT_SRC_PARAGRAPH_BUILDER_H_

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "lib/txt/src/paragraph_style.h"

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_PARAGRAPH_STYLE_H_
#define LIB_TXT_SRC_PARAGRAPH_STYLE_H_
#include <climits>
#include <string>
#include "lib/txt/include/minikin/LineBreaker.h"
#include "lib/txt/src/font_style.h"
#include "lib/txt/src/font_weight.h"
#include "lib/txt/src/text_align.h"
namespace txt {
class ParagraphStyle {
public:
FontWeight font_weight = FontWeight::w400;
FontStyle font_style = FontStyle::normal;
std::string font_family = "";
double font_size = 14;
TextAlign text_align = TextAlign::left;
size_t max_lines = UINT_MAX;
double line_height = 1.0;
std::string ellipsis = "...";
// Default strategy is kBreakStrategy_Greedy. Sometimes,
// kBreakStrategy_HighQuality will produce more desireable layouts (eg, very
// long words are more likely to be reasonably placed).
// kBreakStrategy_Balanced will balance between the two.
minikin::BreakStrategy break_strategy =
minikin::BreakStrategy::kBreakStrategy_Greedy;
// TODO(garyq): Implement right to left.
// Right to left (Arabic, Hebrew, etc).
bool rtl = false;
};
} // namespace txt
#endif // LIB_TXT_SRC_PARAGRAPH_STYLE_H_

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2017 Google, Inc.
*
* 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.
*/
#include "lib/txt/src/styled_runs.h"
#include "lib/ftl/logging.h"
namespace txt {
StyledRuns::StyledRuns() = default;
StyledRuns::~StyledRuns() = default;
StyledRuns::StyledRuns(StyledRuns&& other) {
styles_.swap(other.styles_);
runs_.swap(other.runs_);
}
const StyledRuns& StyledRuns::operator=(StyledRuns&& other) {
styles_.swap(other.styles_);
runs_.swap(other.runs_);
return *this;
}
void StyledRuns::swap(StyledRuns& other) {
styles_.swap(other.styles_);
runs_.swap(other.runs_);
}
size_t StyledRuns::AddStyle(const TextStyle& style) {
const size_t style_index = styles_.size();
styles_.push_back(style);
return style_index;
}
const TextStyle& StyledRuns::PeekStyle() const {
return styles_.back();
}
void StyledRuns::StartRun(size_t style_index, size_t start) {
runs_.push_back(IndexedRun{style_index, start, start});
}
void StyledRuns::EndRunIfNeeded(size_t end) {
if (runs_.empty())
return;
IndexedRun& run = runs_.back();
if (run.start == end) {
// The run is empty. We can skip it.
runs_.pop_back();
} else {
run.end = end;
}
}
StyledRuns::Run StyledRuns::GetRun(size_t index) const {
const IndexedRun& run = runs_[index];
return Run{styles_[run.style_index], run.start, run.end};
}
void StyledRuns::SplitNewlineRuns(std::list<size_t> newline_positions) {
std::vector<IndexedRun> result;
for (size_t i = 0; i < runs_.size(); ++i) {
if (runs_[i].end <= newline_positions.front() ||
newline_positions.empty()) {
result.push_back(runs_[i]);
} else {
size_t start = runs_[i].start;
size_t end = runs_[i].end;
while (end > newline_positions.front() && !newline_positions.empty() &&
start < end) {
IndexedRun temp_run;
temp_run.style_index = runs_[i].style_index;
temp_run.start = start;
temp_run.end = newline_positions.front();
newline_positions.pop_front();
result.push_back(temp_run);
temp_run.start = temp_run.end;
temp_run.end = temp_run.end + 1;
result.push_back(temp_run);
start = temp_run.end;
}
if (start < end) {
IndexedRun temp_run;
temp_run.style_index = runs_[i].style_index;
temp_run.start = start;
temp_run.end = end;
result.push_back(temp_run);
}
}
}
runs_ = result;
}
} // namespace txt

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_STYLED_RUNS_H_
#define LIB_TXT_SRC_STYLED_RUNS_H_
#include <list>
#include <vector>
#include "lib/txt/src/text_style.h"
#include "third_party/gtest/include/gtest/gtest_prod.h"
namespace txt {
// This holds and handles the start/end positions of discrete chunks of text
// that use different styles (a 'run').
class StyledRuns {
public:
struct Run {
const TextStyle& style;
size_t start;
size_t end;
};
StyledRuns();
~StyledRuns();
StyledRuns(const StyledRuns& other) = delete;
StyledRuns(StyledRuns&& other);
const StyledRuns& operator=(StyledRuns&& other);
void swap(StyledRuns& other);
size_t AddStyle(const TextStyle& style);
// Returns the last TextStyle on the stack.
const TextStyle& PeekStyle() const;
void StartRun(size_t style_index, size_t start);
void EndRunIfNeeded(size_t end);
size_t size() const { return runs_.size(); }
Run GetRun(size_t index) const;
// Break any newline '\n' characters into their own runs.
void SplitNewlineRuns(std::list<size_t> newline_positions);
private:
FRIEND_TEST(RenderTest, SimpleParagraph);
FRIEND_TEST(RenderTest, SimpleRedParagraph);
FRIEND_TEST(RenderTest, RainbowParagraph);
FRIEND_TEST(RenderTest, DefaultStyleParagraph);
FRIEND_TEST(RenderTest, BoldParagraph);
FRIEND_TEST(RenderTest, LeftAlignParagraph);
FRIEND_TEST(RenderTest, RightAlignParagraph);
FRIEND_TEST(RenderTest, CenterAlignParagraph);
FRIEND_TEST(RenderTest, JustifyAlignParagraph);
FRIEND_TEST(RenderTest, DecorationsParagraph);
FRIEND_TEST(RenderTest, ItalicsParagraph);
FRIEND_TEST(RenderTest, ChineseParagraph);
FRIEND_TEST(RenderTest, DISABLED_ArabicParagraph);
FRIEND_TEST(RenderTest, LongWordParagraph);
FRIEND_TEST(RenderTest, KernParagraph);
FRIEND_TEST(RenderTest, HyphenBreakParagraph);
FRIEND_TEST(RenderTest, RepeatLayoutParagraph);
struct IndexedRun {
size_t style_index = 0;
size_t start = 0;
size_t end = 0;
};
std::vector<TextStyle> styles_;
std::vector<IndexedRun> runs_;
};
} // namespace txt
#endif // LIB_TXT_SRC_STYLED_RUNS_H_

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_TEXT_ALIGN_H_
#define LIB_TXT_SRC_TEXT_ALIGN_H_
namespace txt {
enum class TextAlign {
left,
right,
center,
justify,
};
} // namespace txt
#endif // LIB_TXT_SRC_TEXT_ALIGN_H_

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_TEXT_BASELINE_H_
#define LIB_TXT_SRC_TEXT_BASELINE_H_
namespace txt {
enum TextBaseline {
kAlphabetic,
kIdeographic,
};
} // namespace txt
#endif // LIB_TXT_SRC_TEXT_BASELINE_H_

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include <algorithm>
#include <vector>
#include "lib/txt/src/text_decoration.h"
namespace txt {} // namespace txt

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_TEXT_DECORATION_H_
#define LIB_TXT_SRC_TEXT_DECORATION_H_
namespace txt {
// Multiple decorations can be applied at once. Ex: Underline and overline is
// (0x1 | 0x2)
enum TextDecoration {
kNone = 0x0,
kUnderline = 0x1,
kOverline = 0x2,
kLineThrough = 0x4,
};
enum TextDecorationStyle { kSolid, kDouble, kDotted, kDashed, kWavy };
} // namespace txt
#endif // LIB_TXT_SRC_TEXT_DECORATION_H_

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include "lib/txt/src/text_style.h"
#include "lib/txt/src/font_style.h"
#include "lib/txt/src/font_weight.h"
#include "third_party/skia/include/core/SkColor.h"
namespace txt {
bool TextStyle::equals(const TextStyle& other) const {
if (color != other.color)
return false;
if (decoration != other.decoration)
return false;
if (decoration_color != other.decoration_color)
return false;
if (decoration_style != other.decoration_style)
return false;
if (decoration_thickness_multiplier != other.decoration_thickness_multiplier)
return false;
if (font_weight != other.font_weight)
return false;
if (font_style != other.font_style)
return false;
if (font_family != other.font_family)
return false;
if (letter_spacing != other.letter_spacing)
return false;
if (word_spacing != other.word_spacing)
return false;
if (height != other.height)
return false;
return true;
}
} // namespace txt

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2017 Google Inc.
*
* 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.
*/
#ifndef LIB_TXT_SRC_TEXT_STYLE_H_
#define LIB_TXT_SRC_TEXT_STYLE_H_
#include <string>
#include "lib/txt/src/font_style.h"
#include "lib/txt/src/font_weight.h"
#include "lib/txt/src/text_baseline.h"
#include "lib/txt/src/text_decoration.h"
#include "third_party/skia/include/core/SkColor.h"
namespace txt {
class TextStyle {
public:
SkColor color = SK_ColorBLACK;
TextDecoration decoration = TextDecoration::kNone;
// Does not make sense to draw a transparent object, so we use it as a default
// value to indicate no decoration color was set.
SkColor decoration_color = SK_ColorTRANSPARENT;
TextDecorationStyle decoration_style = TextDecorationStyle::kSolid;
// Thickness is applied as a multiplier to the default thickness of the font.
double decoration_thickness_multiplier = 1.0;
FontWeight font_weight = FontWeight::w400;
FontStyle font_style = FontStyle::normal;
TextBaseline text_baseline = TextBaseline::kAlphabetic;
std::string font_family = "";
double font_size = 14.0;
double letter_spacing = 0.0;
double word_spacing = 0.0;
double height = 1.0;
bool equals(const TextStyle& other) const;
};
} // namespace txt
#endif // LIB_TXT_SRC_TEXT_STYLE_H_

View File

@@ -0,0 +1,33 @@
filegroup {
name: "minikin-test-data",
srcs: [
"data/Bold.ttf",
"data/BoldItalic.ttf",
"data/ColorEmojiFont.ttf",
"data/ColorTextMixedEmojiFont.ttf",
"data/Emoji.ttf",
"data/Italic.ttf",
"data/Ja.ttf",
"data/Ko.ttf",
"data/MultiAxis.ttf",
"data/NoCmapFormat14.ttf",
"data/NoGlyphFont.ttf",
"data/Regular.ttf",
"data/TextEmojiFont.ttf",
"data/UnicodeBMPOnly.ttf",
"data/UnicodeBMPOnly2.ttf",
"data/UnicodeUCS4.ttf",
"data/VariationSelectorTest-Regular.ttf",
"data/ZhHans.ttf",
"data/ZhHant.ttf",
"data/emoji.xml",
"data/itemize.xml",
],
}
subdirs = [
"perftests",
"stresstest",
"unittest",
"util",
]

View File

@@ -0,0 +1,22 @@
# Copyright 2017 Google, Inc.
#
# 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.
group("tests") {
testonly = true
deps = [
"txt",
"unittest",
]
}

View File

@@ -0,0 +1,49 @@
//
// Copyright (C) 2016 The Android Open Source Project
//
// 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.
//
cc_benchmark {
name: "minikin_perftests",
test_suites: ["device-tests"],
cppflags: [
"-Werror",
"-Wall",
"-Wextra",
],
srcs: [
"FontCollection.cpp",
"FontFamily.cpp",
"FontLanguage.cpp",
"GraphemeBreak.cpp",
"Hyphenator.cpp",
"WordBreaker.cpp",
"main.cpp",
],
header_libs: ["libminikin-headers-for-tests"],
static_libs: [
"libminikin-tests-util",
"libminikin",
"libxml2",
],
shared_libs: [
"libharfbuzz_ng",
"libicuuc",
"liblog",
"libskia",
],
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
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.
-->
<configuration description="Config for minikin_perftests">
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="minikin_perftests->/data/benchmarktest/minikin_perftests" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
<option name="native-benchmark-device-path" value="/data/benchmarktest" />
<option name="benchmark-module-name" value="minikin_perftests" />
</test>
</configuration>

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.
*/
#include <benchmark/benchmark.h>
#include <memory>
#include <minikin/FontCollection.h>
#include <FontTestUtils.h>
#include <UnicodeUtils.h>
#include <MinikinInternal.h>
namespace minikin {
const char* SYSTEM_FONT_PATH = "/system/fonts/";
const char* SYSTEM_FONT_XML = "/system/etc/fonts.xml";
static void BM_FontCollection_construct(benchmark::State& state) {
std::vector<std::shared_ptr<FontFamily>> families =
getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML);
while (state.KeepRunning()) {
std::make_shared<FontCollection>(families);
}
}
BENCHMARK(BM_FontCollection_construct);
static void BM_FontCollection_hasVariationSelector(benchmark::State& state) {
std::shared_ptr<FontCollection> collection(
getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
uint32_t baseCp = state.range(0);
uint32_t vsCp = state.range(1);
char titleBuffer[64];
snprintf(titleBuffer, 64, "hasVariationSelector U+%04X,U+%04X", baseCp, vsCp);
state.SetLabel(titleBuffer);
while (state.KeepRunning()) {
collection->hasVariationSelector(baseCp, vsCp);
}
}
// TODO: Rewrite with BENCHMARK_CAPTURE for better test name.
BENCHMARK(BM_FontCollection_hasVariationSelector)
->ArgPair(0x2708, 0xFE0F)
->ArgPair(0x2708, 0xFE0E)
->ArgPair(0x3402, 0xE0100);
struct ItemizeTestCases {
std::string itemizeText;
std::string languageTag;
std::string labelText;
} ITEMIZE_TEST_CASES[] = {
{ "'A' 'n' 'd' 'r' 'o' 'i' 'd'", "en", "English" },
{ "U+4E16", "zh-Hans", "CJK Ideograph" },
{ "U+4E16", "zh-Hans,zh-Hant,ja,en,es,pt,fr,de", "CJK Ideograph with many language fallback" },
{ "U+3402 U+E0100", "ja", "CJK Ideograph with variation selector" },
{ "'A' 'n' U+0E1A U+0E31 U+0645 U+062D U+0648", "en", "Mixture of English, Thai and Arabic" },
{ "U+2708 U+FE0E", "en", "Emoji with variation selector" },
{ "U+0031 U+FE0F U+20E3", "en", "KEYCAP" },
};
static void BM_FontCollection_itemize(benchmark::State& state) {
std::shared_ptr<FontCollection> collection(
getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
size_t testIndex = state.range(0);
state.SetLabel("Itemize: " + ITEMIZE_TEST_CASES[testIndex].labelText);
uint16_t buffer[64];
size_t utf16_length = 0;
ParseUnicode(
buffer, 64, ITEMIZE_TEST_CASES[testIndex].itemizeText.c_str(), &utf16_length, nullptr);
std::vector<FontCollection::Run> result;
FontStyle style(FontStyle::registerLanguageList(ITEMIZE_TEST_CASES[testIndex].languageTag));
std::lock_guard<std::mutex> _l(gMinikinLock);
while (state.KeepRunning()) {
result.clear();
collection->itemize(buffer, utf16_length, style, &result);
}
}
// TODO: Rewrite with BENCHMARK_CAPTURE once it is available in Android.
BENCHMARK(BM_FontCollection_itemize)
->Arg(0)->Arg(1)->Arg(2)->Arg(3)->Arg(4)->Arg(5)->Arg(6);
} // namespace minikin

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#include <benchmark/benchmark.h>
#include <minikin/FontFamily.h>
#include "../util/MinikinFontForTest.h"
namespace minikin {
static void BM_FontFamily_create(benchmark::State& state) {
std::shared_ptr<MinikinFontForTest> minikinFont =
std::make_shared<MinikinFontForTest>("/system/fonts/NotoSansCJK-Regular.ttc", 0);
while (state.KeepRunning()) {
std::shared_ptr<FontFamily> family = std::make_shared<FontFamily>(
std::vector<Font>({Font(minikinFont, FontStyle())}));
}
}
BENCHMARK(BM_FontFamily_create);
} // namespace minikin

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.
*/
#include <benchmark/benchmark.h>
#include "FontLanguage.h"
namespace minikin {
static void BM_FontLanguage_en_US(benchmark::State& state) {
while (state.KeepRunning()) {
FontLanguage language("en-US", 5);
}
}
BENCHMARK(BM_FontLanguage_en_US);
static void BM_FontLanguage_en_Latn_US(benchmark::State& state) {
while (state.KeepRunning()) {
FontLanguage language("en-Latn-US", 10);
}
}
BENCHMARK(BM_FontLanguage_en_Latn_US);
static void BM_FontLanguage_en_Latn_US_u_em_emoji(benchmark::State& state) {
while (state.KeepRunning()) {
FontLanguage language("en-Latn-US-u-em-emoji", 21);
}
}
BENCHMARK(BM_FontLanguage_en_Latn_US_u_em_emoji);
} // namespace minikin

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.
*/
#include <benchmark/benchmark.h>
#include <cutils/log.h>
#include "minikin/GraphemeBreak.h"
#include "UnicodeUtils.h"
namespace minikin {
const char* ASCII_TEST_STR = "'L' 'o' 'r' 'e' 'm' ' ' 'i' 'p' 's' 'u' 'm' '.'";
// U+261D: WHITE UP POINTING INDEX
// U+1F3FD: EMOJI MODIFIER FITZPATRICK TYPE-4
const char* EMOJI_TEST_STR = "U+261D U+1F3FD U+261D U+1F3FD U+261D U+1F3FD U+261D U+1F3FD";
// U+1F1FA: REGIONAL INDICATOR SYMBOL LETTER U
// U+1F1F8: REGIONAL INDICATOR SYMBOL LETTER S
const char* FLAGS_TEST_STR = "U+1F1FA U+1F1F8 U+1F1FA U+1F1F8 U+1F1FA U+1F1F8";
// TODO: Migrate BENCHMARK_CAPTURE for parameterizing.
static void BM_GraphemeBreak_Ascii(benchmark::State& state) {
size_t result_size;
uint16_t buffer[12];
ParseUnicode(buffer, 12, ASCII_TEST_STR, &result_size, nullptr);
LOG_ALWAYS_FATAL_IF(result_size != 12);
const size_t testIndex = state.range(0);
while (state.KeepRunning()) {
GraphemeBreak::isGraphemeBreak(nullptr, buffer, 0, result_size, testIndex);
}
}
BENCHMARK(BM_GraphemeBreak_Ascii)
->Arg(0) // Begining of the text.
->Arg(1) // Middle of the text.
->Arg(12); // End of the text.
static void BM_GraphemeBreak_Emoji(benchmark::State& state) {
size_t result_size;
uint16_t buffer[12];
ParseUnicode(buffer, 12, EMOJI_TEST_STR, &result_size, nullptr);
LOG_ALWAYS_FATAL_IF(result_size != 12);
const size_t testIndex = state.range(0);
while (state.KeepRunning()) {
GraphemeBreak::isGraphemeBreak(nullptr, buffer, 0, result_size, testIndex);
}
}
BENCHMARK(BM_GraphemeBreak_Emoji)
->Arg(1) // Middle of emoji modifier sequence.
->Arg(2) // Middle of the surrogate pairs.
->Arg(3); // After emoji modifier sequence. Here is boundary of grapheme cluster.
static void BM_GraphemeBreak_Emoji_Flags(benchmark::State& state) {
size_t result_size;
uint16_t buffer[12];
ParseUnicode(buffer, 12, FLAGS_TEST_STR, &result_size, nullptr);
LOG_ALWAYS_FATAL_IF(result_size != 12);
const size_t testIndex = state.range(0);
while (state.KeepRunning()) {
GraphemeBreak::isGraphemeBreak(nullptr, buffer, 0, result_size, testIndex);
}
}
BENCHMARK(BM_GraphemeBreak_Emoji_Flags)
->Arg(2) // Middle of flag sequence.
->Arg(4) // After flag sequence. Here is boundary of grapheme cluster.
->Arg(10); // Middle of 3rd flag sequence.
} // namespace minikin

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.
*/
#include <benchmark/benchmark.h>
#include <minikin/Hyphenator.h>
#include <FileUtils.h>
#include <UnicodeUtils.h>
namespace minikin {
const char* enUsHyph = "/system/usr/hyphen-data/hyph-en-us.hyb";
const int enUsMinPrefix = 2;
const int enUsMinSuffix = 3;
const icu::Locale& usLocale = icu::Locale::getUS();
static void BM_Hyphenator_short_word(benchmark::State& state) {
Hyphenator* hyphenator = Hyphenator::loadBinary(
readWholeFile(enUsHyph).data(), enUsMinPrefix, enUsMinSuffix);
std::vector<uint16_t> word = utf8ToUtf16("hyphen");
std::vector<HyphenationType> result;
while (state.KeepRunning()) {
hyphenator->hyphenate(&result, word.data(), word.size(), usLocale);
}
Hyphenator::loadBinary(nullptr, 2, 2);
}
// TODO: Use BENCHMARK_CAPTURE for parametrise.
BENCHMARK(BM_Hyphenator_short_word);
static void BM_Hyphenator_long_word(benchmark::State& state) {
Hyphenator* hyphenator = Hyphenator::loadBinary(
readWholeFile(enUsHyph).data(), enUsMinPrefix, enUsMinSuffix);
std::vector<uint16_t> word = utf8ToUtf16(
"Pneumonoultramicroscopicsilicovolcanoconiosis");
std::vector<HyphenationType> result;
while (state.KeepRunning()) {
hyphenator->hyphenate(&result, word.data(), word.size(), usLocale);
}
Hyphenator::loadBinary(nullptr, 2, 2);
}
// TODO: Use BENCHMARK_CAPTURE for parametrise.
BENCHMARK(BM_Hyphenator_long_word);
// TODO: Add more tests for other languages.
} // namespace minikin

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.
*/
#include <benchmark/benchmark.h>
#include "minikin/WordBreaker.h"
#include "UnicodeUtils.h"
namespace minikin {
static void BM_WordBreaker_English(benchmark::State& state) {
const char* kLoremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
"eiusmod tempor incididunt ut labore et dolore magna aliqua.";
WordBreaker wb;
wb.setLocale(icu::Locale::getEnglish());
std::vector<uint16_t> text = utf8ToUtf16(kLoremIpsum);
while (state.KeepRunning()) {
wb.setText(text.data(), text.size());
while (wb.next() != -1) {}
}
}
BENCHMARK(BM_WordBreaker_English);
// TODO: Add more tests for other languages.
} // namespace minikin

View File

@@ -0,0 +1,3 @@
mmm -j8 frameworks/minikin/tests/perftests &&
adb sync data &&
adb shell /data/benchmarktest/minikin_perftests/minikin_perftests

Some files were not shown because too many files have changed in this diff Show More