From e2287976f369ed741a2ef8c2954a629a23b003cf Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Mon, 7 May 2018 17:43:07 -0700 Subject: [PATCH] Fabric/Text: attributedstring module, the first part Summary: `fabric/attributedstring` is a simple, cross-platfrom, react-specific implementation of attributed string (aka spanned string). This diff is the first part of this which contains text primitives (types) and conversions. Reviewed By: fkgozali Differential Revision: D7748704 fbshipit-source-id: d76e31807e5ac7ab1a16fd6ee6445c59de5b89a2 --- React.podspec | 10 +- ReactCommon/fabric/attributedstring/BUCK | 78 ++++++++++++ .../attributedstring/ParagraphAttributes.cpp | 37 ++++++ .../attributedstring/ParagraphAttributes.h | 65 ++++++++++ .../fabric/attributedstring/TextPrimitives.h | 94 ++++++++++++++ .../tests/AttributedStringTest.cpp | 18 +++ .../attributedstring/textValuesConversions.h | 120 ++++++++++++++++++ 7 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 ReactCommon/fabric/attributedstring/BUCK create mode 100644 ReactCommon/fabric/attributedstring/ParagraphAttributes.cpp create mode 100644 ReactCommon/fabric/attributedstring/ParagraphAttributes.h create mode 100644 ReactCommon/fabric/attributedstring/TextPrimitives.h create mode 100644 ReactCommon/fabric/attributedstring/tests/AttributedStringTest.cpp create mode 100644 ReactCommon/fabric/attributedstring/textValuesConversions.h diff --git a/React.podspec b/React.podspec index 030989c3d..b985ee3ea 100644 --- a/React.podspec +++ b/React.podspec @@ -149,6 +149,15 @@ Pod::Spec.new do |s| end s.subspec "fabric" do |ss| + ss.subspec "attributedstring" do |sss| + sss.dependency "Folly", folly_version + sss.compiler_flags = folly_compiler_flags + sss.source_files = "ReactCommon/fabric/attributedstring/**/*.{cpp,h}" + sss.exclude_files = "**/tests/*" + sss.header_dir = "fabric/attributedstring" + sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_ROOT)/Folly\"" } + end + ss.subspec "core" do |sss| sss.dependency "Folly", folly_version sss.compiler_flags = folly_compiler_flags @@ -194,7 +203,6 @@ Pod::Spec.new do |s| sss.header_dir = "fabric/view" sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_ROOT)/Folly\"" } end - end s.subspec "ART" do |ss| diff --git a/ReactCommon/fabric/attributedstring/BUCK b/ReactCommon/fabric/attributedstring/BUCK new file mode 100644 index 000000000..625639db3 --- /dev/null +++ b/ReactCommon/fabric/attributedstring/BUCK @@ -0,0 +1,78 @@ +load("//configurations/buck/apple:flag_defs.bzl", "get_debug_preprocessor_flags") +load("//ReactNative:DEFS.bzl", "IS_OSS_BUILD", "react_native_xplat_target", "rn_xplat_cxx_library", "get_apple_inspector_flags", "APPLE") + +APPLE_COMPILER_FLAGS = [] + +if not IS_OSS_BUILD: + load("@xplat//configurations/buck/apple:flag_defs.bzl", "get_static_library_ios_flags", "flags") + APPLE_COMPILER_FLAGS = flags.get_flag_value(get_static_library_ios_flags(), 'compiler_flags') + +rn_xplat_cxx_library( + name = "attributedstring", + srcs = glob( + ["**/*.cpp"], + excludes = glob(["tests/**/*.cpp"]), + ), + headers = glob( + ["**/*.h"], + excludes = glob(["tests/**/*.h"]), + ), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("", "*.h"), + ], + prefix = "fabric/attributedstring", + ), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, + fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), + fbobjc_tests = [ + ":tests", + ], + force_static = True, + macosx_tests_override = [], + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + tests = [], + visibility = ["PUBLIC"], + deps = [ + "xplat//fbsystrace:fbsystrace", + "xplat//folly:headers_only", + "xplat//folly:memory", + "xplat//folly:molly", + "xplat//third-party/glog:glog", + react_native_xplat_target("fabric/debug:debug"), + react_native_xplat_target("fabric/core:core"), + react_native_xplat_target("fabric/graphics:graphics"), + ], +) + +if not IS_OSS_BUILD: + load("@xplat//build_defs:fb_xplat_cxx_test.bzl", "fb_xplat_cxx_test") + + fb_xplat_cxx_test( + name = "tests", + srcs = glob(["tests/**/*.cpp"]), + headers = glob(["tests/**/*.h"]), + contacts = ["oncall+react_native@xmail.facebook.com"], + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + platforms = APPLE, + deps = [ + "xplat//folly:molly", + "xplat//third-party/gmock:gtest", + ":attributedstring", + ], + ) diff --git a/ReactCommon/fabric/attributedstring/ParagraphAttributes.cpp b/ReactCommon/fabric/attributedstring/ParagraphAttributes.cpp new file mode 100644 index 000000000..3901c18cf --- /dev/null +++ b/ReactCommon/fabric/attributedstring/ParagraphAttributes.cpp @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "ParagraphAttributes.h" + +#include +#include + +namespace facebook { +namespace react { + +#pragma mark - DebugStringConvertible + +SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const { + ParagraphAttributes defaultParagraphAttributes = {}; + SharedDebugStringConvertibleList list = {}; + +#define PARAGRAPH_ATTRIBUTE(stringName, propertyName, accessor, convertor) \ + if (propertyName != defaultParagraphAttributes.propertyName) { \ + list.push_back(std::make_shared(#stringName, convertor(propertyName accessor))); \ + } + + PARAGRAPH_ATTRIBUTE(maximumNumberOfLines, maximumNumberOfLines, , std::to_string) + PARAGRAPH_ATTRIBUTE(ellipsizeMode, ellipsizeMode, , stringFromEllipsizeMode) + PARAGRAPH_ATTRIBUTE(adjustsFontSizeToFit, adjustsFontSizeToFit, , std::to_string) + PARAGRAPH_ATTRIBUTE(minimumFontSize, minimumFontSize, , std::to_string) + PARAGRAPH_ATTRIBUTE(maximumFontSize, maximumFontSize, , std::to_string) + + return list; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/attributedstring/ParagraphAttributes.h b/ReactCommon/fabric/attributedstring/ParagraphAttributes.h new file mode 100644 index 000000000..42eb1aa0a --- /dev/null +++ b/ReactCommon/fabric/attributedstring/ParagraphAttributes.h @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace facebook { +namespace react { + +class ParagraphAttributes; + +using SharedParagraphAttributes = std::shared_ptr; + +/* + * Represents all visual attributes of a paragraph of text. + * Two data structures, ParagraphAttributes and AttributedText, should be + * enough to define visual representation of a piece of text on the screen. + */ +class ParagraphAttributes: + public DebugStringConvertible { + +public: + +#pragma mark - Fields + + /* + * Maximum number of lines which paragraph can take. + * Zero value represents "no limit". + */ + int maximumNumberOfLines {0}; + + /* + * In case if a text cannot fit given boudaures, defines a place where + * an ellipsize should be placed. + */ + EllipsizeMode ellipsizeMode {EllipsizeMode::Clip}; + + /* + * Enables font size adjustment to fit constrained boundaries. + */ + bool adjustsFontSizeToFit {false}; + + /* + * In case of font size adjustment enabled, defines minimum and maximum + * font sizes. + */ + Float minimumFontSize {std::numeric_limits::quiet_NaN()}; + Float maximumFontSize {std::numeric_limits::quiet_NaN()}; + +#pragma mark - DebugStringConvertible + + SharedDebugStringConvertibleList getDebugProps() const override; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/attributedstring/TextPrimitives.h b/ReactCommon/fabric/attributedstring/TextPrimitives.h new file mode 100644 index 000000000..7d6d0a79b --- /dev/null +++ b/ReactCommon/fabric/attributedstring/TextPrimitives.h @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace facebook { +namespace react { + +enum class FontStyle { + Normal, + Italic, + Oblique +}; + +enum class FontWeight: int { + Weight100 = 100, + UltraLight = 100, + Weight200 = 200, + Thin = 200, + Weight300 = 300, + Light = 300, + Weight400 = 400, + Regular = 400, + Weight500 = 500, + Medium = 500, + Weight600 = 600, + Semibold = 600, + Demibold = 600, + Weight700 = 700, + Bold = 700, + Weight800 = 800, + Heavy = 800, + Weight900 = 900, + Black = 900 +}; + +enum class FontVariant: int { + Default = 0, + SmallCaps = 1 << 1, + OldstyleNums = 1 << 2, + LiningNums = 1 << 3, + TabularNums = 1 << 4, + ProportionalNums = 1 << 5 +}; + +enum class EllipsizeMode { + Clip, // Do not add ellipsize, simply clip. + Head, // Truncate at head of line: "...wxyz". + Tail, // Truncate at tail of line: "abcd...". + Middle // Truncate middle of line: "ab...yz". +}; + +enum class TextAlignment { + Natural, // Indicates the default alignment for script. + Left, // Visually left aligned. + Center, // Visually centered. + Right, // Visually right aligned. + Justified // Fully-justified. The last line in a paragraph is natural-aligned. +}; + +enum class WritingDirection { + Natural, // Determines direction using the Unicode Bidi Algorithm rules P2 and P3. + LeftToRight, // Left to right writing direction. + RightToLeft // Right to left writing direction. +}; + +enum class TextDecorationLineType { + None, + Underline, + Strikethrough, + UnderlineStrikethrough +}; + +enum class TextDecorationLineStyle { + Single, + Thick, + Double +}; + +enum class TextDecorationLinePattern { + Solid, + Dot, + Dash, + DashDot, + DashDotDot, +}; + +} // namespace react +} // namespace facebook + diff --git a/ReactCommon/fabric/attributedstring/tests/AttributedStringTest.cpp b/ReactCommon/fabric/attributedstring/tests/AttributedStringTest.cpp new file mode 100644 index 000000000..b3a8eddd3 --- /dev/null +++ b/ReactCommon/fabric/attributedstring/tests/AttributedStringTest.cpp @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include + +#include + +using namespace facebook::react; + +TEST(AttributedStringTest, testSomething) { + // TODO +} diff --git a/ReactCommon/fabric/attributedstring/textValuesConversions.h b/ReactCommon/fabric/attributedstring/textValuesConversions.h new file mode 100644 index 000000000..88480b6ff --- /dev/null +++ b/ReactCommon/fabric/attributedstring/textValuesConversions.h @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +inline std::string stringFromEllipsizeMode(const EllipsizeMode &ellipsisMode) { + switch (ellipsisMode) { + case EllipsizeMode::Clip: return "clip"; + case EllipsizeMode::Head: return "head"; + case EllipsizeMode::Tail: return "tail"; + case EllipsizeMode::Middle: return "middle"; + } +} + +inline EllipsizeMode ellipsizeModeFromDynamic(const folly::dynamic &value) { + auto string = value.getString(); + if (string == "clip") { return EllipsizeMode::Clip; } + if (string == "head") { return EllipsizeMode::Head; } + if (string == "tail") { return EllipsizeMode::Tail; } + if (string == "middle") { return EllipsizeMode::Middle; } + abort(); +} + +inline FontWeight fontWeightFromDynamic(const folly::dynamic &value) { + auto string = value.asString(); + if (string == "normal") { return FontWeight::Regular; } + if (string == "regular") { return FontWeight::Regular; } + if (string == "bold") { return FontWeight::Bold; } + if (string == "100") { return FontWeight::Weight100; } + if (string == "200") { return FontWeight::Weight200; } + if (string == "300") { return FontWeight::Weight300; } + if (string == "400") { return FontWeight::Weight400; } + if (string == "500") { return FontWeight::Weight500; } + if (string == "600") { return FontWeight::Weight600; } + if (string == "700") { return FontWeight::Weight700; } + if (string == "800") { return FontWeight::Weight800; } + if (string == "900") { return FontWeight::Weight900; } + abort(); +} + +inline FontStyle fontStyleFromDynamic(const folly::dynamic &value) { + auto string = value.asString(); + if (string == "normal") { return FontStyle::Normal; } + if (string == "italic") { return FontStyle::Italic; } + if (string == "oblique") { return FontStyle::Oblique; } + abort(); +} + +inline FontVariant fontVariantFromDynamic(const folly::dynamic &value) { + assert(value.isArray()); + FontVariant fontVariant = FontVariant::Default; + for (auto &&item : value) { + auto string = item.asString(); + if (string == "small-caps") { fontVariant = (FontVariant)((int)fontVariant | (int)FontVariant::SmallCaps); continue; } + if (string == "oldstyle-nums") { fontVariant = (FontVariant)((int)fontVariant | (int)FontVariant::OldstyleNums); continue; } + if (string == "lining-nums") { fontVariant = (FontVariant)((int)fontVariant | (int)FontVariant::LiningNums); continue; } + if (string == "tabular-nums") { fontVariant = (FontVariant)((int)fontVariant | (int)FontVariant::TabularNums); continue; } + if (string == "proportional-nums") { fontVariant = (FontVariant)((int)fontVariant | (int)FontVariant::ProportionalNums); continue; } + } + return fontVariant; +} + +inline TextAlignment textAlignmentFromDynamic(const folly::dynamic &value) { + auto string = value.asString(); + if (string == "natural") { return TextAlignment::Natural; } + if (string == "left") { return TextAlignment::Left; } + if (string == "center") { return TextAlignment::Center; } + if (string == "right") { return TextAlignment::Right; } + if (string == "justified") { return TextAlignment::Justified; } + abort(); +} + +inline WritingDirection writingDirectionFromDynamic(const folly::dynamic &value) { + auto string = value.asString(); + if (string == "natural") { return WritingDirection::Natural; } + if (string == "ltr") { return WritingDirection::LeftToRight; } + if (string == "rtl") { return WritingDirection::RightToLeft; } + abort(); +} + +inline TextDecorationLineType textDecorationLineTypeFromDynamic(const folly::dynamic &value) { + auto string = value.asString(); + if (string == "none") { return TextDecorationLineType::None; } + if (string == "underline") { return TextDecorationLineType::Underline; } + if (string == "strikethrough") { return TextDecorationLineType::Strikethrough; } + if (string == "underline-strikethrough") { return TextDecorationLineType::UnderlineStrikethrough; } + abort(); +} + +inline TextDecorationLineStyle textDecorationLineStyleFromDynamic(const folly::dynamic &value) { + auto string = value.asString(); + if (string == "single") { return TextDecorationLineStyle::Single; } + if (string == "thick") { return TextDecorationLineStyle::Thick; } + if (string == "double") { return TextDecorationLineStyle::Double; } + abort(); +} + +inline TextDecorationLinePattern textDecorationLinePatternFromDynamic(const folly::dynamic &value) { + auto string = value.asString(); + if (string == "solid") { return TextDecorationLinePattern::Solid; } + if (string == "dot") { return TextDecorationLinePattern::Dot; } + if (string == "dash") { return TextDecorationLinePattern::Dash; } + if (string == "dash-dot") { return TextDecorationLinePattern::DashDot; } + if (string == "dash-dot-dot") { return TextDecorationLinePattern::DashDotDot; } + abort(); +} + +} // namespace react +} // namespace facebook