From 9af3c138a3782e13aca1c27cdae84c62b84ba192 Mon Sep 17 00:00:00 2001 From: Young-il Choi Date: Sun, 12 May 2013 01:59:07 +0900 Subject: [PATCH 1/2] build: android support --- Makefile.in | 25 ++-- configure | 344 ++++++++++++++++++++++++++++++++++++---------------- mk/sub.mk | 32 ++++- 3 files changed, 287 insertions(+), 114 deletions(-) diff --git a/Makefile.in b/Makefile.in index c1b1ac83e38e..9c8b4fd8471a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -29,7 +29,7 @@ B := $(CFG_BUILD_DIR) MKFILE_DEPS := config.stamp $(call rwildcard,$(S)mk/,*) -CFG_RUSTC_FLAGS := $(RUSTFLAGS) -A default_methods +CFG_RUSTC_FLAGS += -A default_methods ifdef CFG_DISABLE_OPTIMIZE $(info cfg: disabling rustc optimization (CFG_DISABLE_OPTIMIZE)) @@ -40,17 +40,24 @@ endif ifdef CFG_ENABLE_DEBUG $(info cfg: enabling more debugging (CFG_ENABLE_DEBUG)) - CFG_RUSTC_FLAGS += + CFG_RUSTC_FLAGS += -cfg debug CFG_GCCISH_CFLAGS += -DRUST_DEBUG else CFG_GCCISH_CFLAGS += -DRUST_NDEBUG endif -export CFG_RUSTC -export CFG_RUSTC_FLAGS -export CFG_LOCAL_RUSTC +export CFLAGS="-fPIC" +export LDFLAGS="-fPIC" export RUSTC=$(CFG_RUSTC) export RUSTFLAGS=$(CFG_RUSTC_FLAGS) +export CC=$(CFG_CC) +export CXX=$(CFG_CXX) +export LD=$(CFG_LD) +export AR=$(CFG_AR) +export RANLIB=$(CFG_RANLIB) +export CFG_OSTYPE +export CFG_CPUTYPE +export CFG_TARGET_TRIPLE ###################################################################### # Re-configuration @@ -246,7 +253,7 @@ servo: $(DEPS_servo) # Darwin app packaging -ifeq ($(OSTYPE),apple-darwin) +ifeq ($(CFG_OSTYPE),apple-darwin) package: servo mkdir -p Servo.app/Contents/MacOS/src/rust-cocoa @@ -256,8 +263,12 @@ package: servo cp src/rust-cocoa/lib*.dylib Servo.app/Contents/MacOS/src/rust-cocoa/ cp src/rust-azure/lib*.dylib Servo.app/Contents/MacOS/src/rust-azure/ -else +else ifeq ($(CFG_OSTYPE),linux-androideabi) +package: $(DEPS_servo) + $(RUSTC) $(RUSTFLAGS) -o libservo.so $< --lib + +else bindings: $(AUTOGEN_SRC_servo) .PHONY: package diff --git a/configure b/configure index 18918506d481..db481875ba91 100755 --- a/configure +++ b/configure @@ -179,7 +179,83 @@ opt() { OP="$OP=<$META>" fi printf " --%-30s %s\n" "$FLAG-$OP" "$DOC" - fi + fi +} + +split_triple() { + local TRIPLE=$1 + local ARCH=$2 + local VENDOR=$3 + local OS=$4 + + eval $ARCH=$(echo "$TRIPLE" | cut -d'-' -f1) + eval $VENDOR=$(echo "$TRIPLE" | cut -d'-' -f2) + eval $OS=$(echo "$TRIPLE" | cut -d'-' -f3) + + if [ $(echo "$TRIPLE" | cut -d'-' -f3) = "androideabi" ] + then + eval $OS="android" + fi +} + +os_type() { + # The goal here is to come up with the same triple as LLVM would, + # at least for the subset of platforms we're willing to target. + + local OP=$1 + local OSTYPE=$(echo "$2" | tr '[:upper:]' '[:lower:]') + local V="${OP}" + + case $OSTYPE in + + linux) + eval $V=unknown-linux-gnu + ;; + + freebsd) + eval $V=unknown-freebsd + ;; + + darwin) + eval $V=apple-darwin + ;; + + mingw32*) + eval $V=pc-mingw32 + ;; + + android) + eval $V=linux-androideabi + ;; + + *) + err "unknown OS type: $OSTYPE" + ;; + esac +} + +cpu_type() { + local OP=$1 + local CPUTYPE=$2 + local V="${OP}" + + case $CPUTYPE in + + i386 | i486 | i686 | i786 | x86) + eval $V=i686 + ;; + + xscale | arm) + eval $V=arm + ;; + + x86_64 | x86-64 | x64 | amd64) + eval $V=x86_64 + ;; + + *) + err "unknown CPU type: $CPUTYPE" + esac } msg "looking for configure programs" @@ -198,70 +274,29 @@ need_cmd sed msg "inspecting environment" -CFG_OSTYPE=$(uname -s) -CFG_CPUTYPE=$(uname -m) +OSTYPE=$(uname -s) +CPUTYPE=$(uname -m) -if [ $CFG_OSTYPE = Darwin -a $CFG_CPUTYPE = i386 ] +if [ $OSTYPE = Darwin -a $CPUTYPE = i386 ] then # Darwin's `uname -s` lies and always returns i386. We have to use sysctl # instead. if sysctl hw.optional.x86_64 | grep -q ': 1' then - CFG_CPUTYPE=x86_64 + CPUTYPE=x86_64 fi fi -# The goal here is to come up with the same triple as LLVM would, -# at least for the subset of platforms we're willing to target. - -case $CFG_OSTYPE in +os_type CFG_BUILD_OSTYPE ${OSTYPE} +cpu_type CFG_BUILD_CPUTYPE ${CPUTYPE} - Linux) - CFG_OSTYPE=unknown-linux-gnu - ;; - - FreeBSD) - CFG_OSTYPE=unknown-freebsd - ;; - - Darwin) - CFG_OSTYPE=apple-darwin - ;; - - MINGW32*) - CFG_OSTYPE=pc-mingw32 - ;; - - *) - err "unknown OS type: $CFG_OSTYPE" - ;; -esac - - -case $CFG_CPUTYPE in - - i386 | i486 | i686 | i786 | x86) - CFG_CPUTYPE=i686 - ;; - - xscale | arm) - CFG_CPUTYPE=arm - ;; - - x86_64 | x86-64 | x64 | amd64) - CFG_CPUTYPE=x86_64 - ;; - - *) - err "unknown CPU type: $CFG_CPUTYPE" -esac - -DEFAULT_HOST_TRIPLE="${CFG_CPUTYPE}-${CFG_OSTYPE}" +DEFAULT_TARGET_TRIPLE="${CFG_BUILD_CPUTYPE}-${CFG_BUILD_OSTYPE}" CFG_SRC_DIR="$(cd $(dirname $0) && pwd)/" CFG_BUILD_DIR="$(pwd)/" CFG_SELF=${CFG_SRC_DIR}$(basename $0) CFG_CONFIGURE_ARGS="$@" +CFG_PATH=${PATH} OPTIONS="" HELP=0 @@ -287,6 +322,12 @@ opt manage-submodules 1 "let the build manage the git submodules" opt fast-make 0 "use .gitmodules as timestamp for submodule deps" opt debug 0 "use debugging symbols" valopt local-rust-root "" "set prefix for local rust binary" +valopt target-triple "${DEFAULT_TARGET_TRIPLE}" "target triple to be compiled" +valopt android-cross-path "/opt/ndk_standalone" "Android NDK cross compiler path" +valopt android-ndk-path "/opt/android-ndk" "Android NDK path" +valopt android-sdk-path "/opt/android-sdk" "Android SDK path" +valopt android-font-path "/system/fonts" "Android Font path" +valopt android-resource-path "/sdcard/servo" "Android Resource path" if [ $HELP -eq 1 ] then @@ -294,13 +335,38 @@ then exit 0 fi +split_triple "${CFG_TARGET_TRIPLE}" TARGET_CPUTYPE TARGET_VENDOR TARGET_OSTYPE +os_type CFG_OSTYPE ${TARGET_OSTYPE} +cpu_type CFG_CPUTYPE ${TARGET_CPUTYPE} + step_msg "looking for build programs" +case ${TARGET_OSTYPE} in + android) + CFG_PATH="${CFG_ANDROID_CROSS_PATH}/bin":$PATH + export PATH=${CFG_PATH} + + probe CFG_CC arm-linux-androideabi-gcc + probe CFG_CXX arm-linux-androideabi-g++ + probe CFG_LD arm-linux-androideabi-ld + probe CFG_AR arm-linux-androideabi-ar + probe CFG_RANLIB arm-linux-androideabi-ranlib + CFG_RUSTC_FLAGS="--target=${CFG_TARGET_TRIPLE} --android-cross-path=${CFG_ANDROID_CROSS_PATH}" + ;; + *) + CFG_PATH=$PATH + probe CFG_CC gcc + probe CFG_CXX g++ + probe CFG_LD ld + probe CFG_AR ar + probe CFG_RANLIB ranlib + CFG_RUSTC_FLAGS="" + ;; +esac + +probe CFG_CLANG clang++ probe_need CFG_GIT git probe_need CFG_PYTHON2 python2 python2.7 python -probe CFG_CLANG clang++ -probe CFG_GCC gcc -probe CFG_LD ld # Spidermonkey requires autoconf 2.13 exactly probe_need CFG_AUTOCONF213 autoconf213 \ autoconf2.13 \ @@ -320,10 +386,10 @@ then else step_msg "using in-tree rust compiler" # The Rust compiler we're going to build - CFG_RUSTC="${CFG_BUILD_DIR}src/rust/${DEFAULT_HOST_TRIPLE}/stage2/bin/rustc" + CFG_RUSTC="${CFG_BUILD_DIR}src/rust/${DEFAULT_TARGET_TRIPLE}/stage2/bin/rustc" fi -if [ -z "$CFG_ENABLE_CLANG" -a -z "$CFG_GCC" ] +if [ -z "$CFG_ENABLE_CLANG" -a -z "$CFG_CC" ] then err "either clang or gcc is required" fi @@ -373,6 +439,12 @@ AUTOCONF213_M4_MACROS="$(dirname ${CFG_AUTOCONF213})/../share/$(basename ${CFG_A # Run the SpiderMonkey autoconf using autoconf 2.13 (cd ${CFG_SRC_DIR}src/mozjs/js/src && "${CFG_AUTOCONF213}" -l "${AUTOCONF213_M4_MACROS}") || exit $? +if [ $CFG_OSTYPE = "linux-androideabi" ] +then + msg "configuring src/libexpat" + (cd ${CFG_SRC_DIR}src/libexpat/expat && sh "buildconf.sh") || exit $? +fi + # Pixman and cairo require some care to autoconf correctly for our in-tree build. # The normal autogen.sh files mostly just run autoreconfig but we need more fine control @@ -399,15 +471,43 @@ AUTOCMD="${LIBTOOLIZE} && autoconf && autoheader && automake --add-missing --cop CFG_SUBMODULES="libwapcaplet rust-wapcaplet rust-harfbuzz rust-opengles skia rust-azure rust-stb-image rust-geom rust-glut rust-layers rust-http-client libparserutils libhubbub libcss rust-netsurfcss rust-css rust-hubbub sharegl rust-mozjs mozjs" -if [ $CFG_OSTYPE = "apple-darwin" ] -then -CFG_SUBMODULES="rust-cocoa rust-io-surface rust-core-foundation rust-core-graphics rust-core-text ${CFG_SUBMODULES}" -fi +case ${CFG_OSTYPE} in + apple-darwin) + CFG_SUBMODULES="rust-cocoa rust-io-surface rust-core-foundation rust-core-graphics rust-core-text ${CFG_SUBMODULES}" + ;; + unknown-linux-gnu) + CFG_SUBMODULES="rust-freetype rust-fontconfig rust-xlib ${CFG_SUBMODULES}" + ;; + linux-androideabi) + CFG_SUBMODULES="libexpat libfreetype2 fontconfig rust-freetype rust-fontconfig ${CFG_SUBMODULES}" + ;; + *) + ;; +esac -if [ $CFG_OSTYPE = "unknown-linux-gnu" ] -then -CFG_SUBMODULES="rust-freetype rust-fontconfig rust-xlib ${CFG_SUBMODULES}" -fi +step_msg "writing configuration" + +#putvar DEFAULT_TARGET_TRIPLE +putvar CFG_TARGET_TRIPLE +putvar CFG_CPUTYPE +putvar CFG_OSTYPE +putvar CFG_SRC_DIR +putvar CFG_BUILD_DIR +putvar CFG_CONFIGURE_ARGS +putvar CFG_SUBMODULES +putvar CFG_DISABLE_MANAGE_SUBMODULES +putvar CFG_PATH +putvar CFG_RUSTC +putvar CFG_RUSTC_FLAGS +putvar CFG_LOCAL_RUSTC +putvar CFG_ENABLE_DEBUG + +msg +copy_if_changed ${CFG_SRC_DIR}Makefile.in ${CFG_BUILD_DIR}Makefile +move_if_changed ${CFG_SRC_DIR}config.tmp ${CFG_SRC_DIR}config.mk +copy_if_changed ${CFG_SRC_DIR}config.mk ${CFG_BUILD_DIR}config.mk +rm -f ${CFG_SRC_DIR}config.tmp +touch ${CFG_SRC_DIR}config.stamp step_msg "making build directories" @@ -445,68 +545,100 @@ done if [ ${do_reconfigure} -ne 0 ] then cd ${CFG_BUILD_DIR}src/rust - ${CFG_SRC_DIR}src/rust/configure + + L_ARGS="" + if [ ${CFG_OSTYPE} = "linux-androideabi" ]; then + L_ARGS="--android-cross-path=${CFG_ANDROID_CROSS_PATH}" + fi + ${CFG_SRC_DIR}src/rust/configure --target-triples=${CFG_TARGET_TRIPLE} ${L_ARGS} cd ${CFG_BUILD_DIR} fi -# Cairo expects to use an installed pixman, but we want to override that -# behavior to use our version -export pixman_CFLAGS="-I${CFG_SRC_DIR}src/pixman/pixman -I${CFG_BUILD_DIR}src/pixman/pixman" -export pixman_LDFLAGS="-I${CFG_BUILD_DIR}src/pixman/pixman/.libs" +#fontconfig expects to use an installed freetype, but we want to override that behavior to use our version +if [ $CFG_OSTYPE = "linux-androideabi" ] +then + export FREETYPE_CFLAGS="-I${CFG_SRC_DIR}src/libfreetype2/include -I${CFG_BUILD_DIR}src/libfreetype2/include" + export FREETYPE_LIBS="-L${CFG_BUILD_DIR}src/libfreetype2/.libs -lfreetype" +fi +export CFG_CONFIG_MK="${CFG_BUILD_DIR}config.mk" -# PIC all the things -export CFLAGS="${CFLAGS} -fPIC" -export LDFLAGS="${CFLAGS} -fPIC" for i in ${CFG_SUBMODULES} do + step_msg "submodule configure: ${i}" + if [ -d ${CFG_BUILD_DIR}src/${i} ] then cd ${CFG_BUILD_DIR}src/${i} fi + CONFIGURE_SCRIPT="${CFG_SRC_DIR}src/${i}/configure" - # needed because Spidermonkey configure is in non-standard location - if [ $i = mozjs ]; then - CONFIGURE_SCRIPT="${CFG_SRC_DIR}src/${i}/js/src/configure" + CONFIGURE_ARGS="" + ENV_VARS="" + + if [ ! -z $CFG_ENABLE_DEBUG ]; then + CONFIGURE_ARGS="${CONFIGURE_ARGS} --enable-debug" fi - # needed because Azure's configure wants "--enable-skia" - CONFIGURE_ARGS="" - ENV_VARS="" - if [ $i = "rust-azure" ]; then - CONFIGURE_ARGS="--enable-skia" - fi - if [ $i = mozjs ]; then - if [ ! -z $CFG_ENABLE_DEBUG ]; then - CONFIGURE_ARGS="--enable-debug" + case $i in + libexpat) + CONFIGURE_SCRIPT="${CFG_SRC_DIR}src/${i}/expat/configure" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --host=arm-linux-androideabi" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-sysroot=${CFG_ANDROID_CROSS_PATH}/sysroot" + CONFIGURE_ARGS="${CONFIGURE_ARGS} CC=${CFG_CC}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} CXX=${CFG_CXX}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} AR=${CFG_AR}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} LD=${CFG_LD}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} RANLIB=${CFG_RANLIB}" + ;; + libfreetype2) + CONFIGURE_ARGS="${CONFIGURE_ARGS} --host=arm-linux" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-sysroot=${CFG_ANDROID_CROSS_PATH}/sysroot" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --without-zlib" + CONFIGURE_ARGS="${CONFIGURE_ARGS} CC=${CFG_CC}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} CXX=${CFG_CXX}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} AR=${CFG_AR}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} LD=${CFG_LD}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} RANLIB=${CFG_RANLIB}" + ;; + fontconfig) + CONFIGURE_SCRIPT="${CFG_SRC_DIR}src/${i}/autogen.sh" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --host=arm-linux-androideabi" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-arch=arm" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-expat-includes=${CFG_SRC_DIR}src/libexpat/expat/lib" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-expat-lib=${CFG_BUILD_DIR}src/libexpat/.libs" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-sysroot=${CFG_ANDROID_CROSS_PATH}/sysroot" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-cache-dir=${CFG_ANDROID_RESOURCE_PATH}/.fccache" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-confdir=${CFG_ANDROID_RESOURCE_PATH}/.fcconfig" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-default-fonts=${CFG_ANDROID_FONT_PATH}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} CC=${CFG_CC}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} CXX=${CFG_CXX}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} AR=${CFG_AR}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} LD=${CFG_LD}" + CONFIGURE_ARGS="${CONFIGURE_ARGS} RANLIB=${CFG_RANLIB}" + ;; + mozjs) + # needed because Spidermonkey configure is in non-standard location + CONFIGURE_SCRIPT="${CFG_SRC_DIR}src/${i}/js/src/configure" + if [ ${CFG_OSTYPE} = "linux-androideabi" ]; then + CONFIGURE_ARGS="${CONFIGURE_ARGS} --target=arm-linux-androideabi" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --with-android-ndk=${CFG_ANDROID_NDK_PATH}" fi - fi + ;; + rust-azure) + # needed because Azure's configure wants "--enable-skia" + CONFIGURE_ARGS="${CONFIGURE_ARGS} --enable-skia" + ;; + *) + ;; + esac + + step_msg "${ENV_VARS} ${CONFIGURE_SCRIPT} ${CONFIGURE_ARGS}" if [ -f ${CONFIGURE_SCRIPT} ] then - (sh ${CONFIGURE_SCRIPT} ${CONFIGURE_ARGS}) || exit $? + (sh ${CONFIGURE_SCRIPT} ${CONFIGURE_ARGS}) || exit $? fi done -step_msg "writing configuration" - -putvar DEFAULT_HOST_TRIPLE -putvar CFG_CPUTYPE -putvar CFG_OSTYPE -putvar CFG_SRC_DIR -putvar CFG_BUILD_DIR -putvar CFG_CONFIGURE_ARGS -putvar CFG_SUBMODULES -putvar CFG_DISABLE_MANAGE_SUBMODULES -putvar CFG_RUSTC -putvar CFG_LOCAL_RUSTC -putvar CFG_ENABLE_DEBUG - -msg -copy_if_changed ${CFG_SRC_DIR}Makefile.in ${CFG_BUILD_DIR}Makefile -move_if_changed ${CFG_SRC_DIR}config.tmp ${CFG_SRC_DIR}config.mk -copy_if_changed ${CFG_SRC_DIR}config.mk ${CFG_BUILD_DIR}config.mk -rm -f ${CFG_SRC_DIR}config.tmp -touch ${CFG_SRC_DIR}config.stamp - step_msg "complete" diff --git a/mk/sub.mk b/mk/sub.mk index e636d15e61b3..0a5ba34361d3 100644 --- a/mk/sub.mk +++ b/mk/sub.mk @@ -126,7 +126,6 @@ DEPS_rust-layers += \ endif ifeq ($(CFG_OSTYPE),unknown-linux-gnu) - DEPS_rust-azure += \ rust-freetype \ rust-fontconfig \ @@ -140,3 +139,34 @@ DEPS_rust-layers += \ rust-xlib \ $(NULL) endif + +ifeq ($(CFG_OSTYPE),linux-androideabi) +DEPS_rust-azure += \ + rust-freetype \ + rust-fontconfig \ + $(NULL) + +DEPS_rust-layers += \ + rust-freetype \ + rust-fontconfig \ + $(NULL) + +DEPS_rust-fontconfig += \ + fontconfig \ + $(NULL) + +DEPS_fontconfig += \ + libexpat \ + libfreetype2 \ + $(NULL) + +DONE_libexpat = "$(B)src/libexpat/.libs/libexpat.so" +DONE_libfreetype2 = "$(B)src/libfreetype2/.libs/libfreetype.so" +DONE_fontconfig = "$(B)src/fontconfig/src/.libs/libfontconfig.so" + +NATIVE_BUILDS += \ + libfreetype2 \ + libexpat \ + fontconfig \ + $(NULL) +endif From a196dc1730aa912a2d5cbee536cbba422bf95c88 Mon Sep 17 00:00:00 2001 From: Young-il Choi Date: Sun, 12 May 2013 02:00:09 +0900 Subject: [PATCH 2/2] servo-gfx: android specific adaptation --- src/servo-gfx/font.rs | 7 + src/servo-gfx/platform/android/font.rs | 297 ++++++++++++++++++ .../platform/android/font_context.rs | 55 ++++ src/servo-gfx/platform/android/font_list.rs | 182 +++++++++++ src/servo-gfx/platform/mod.rs | 7 + src/servo-gfx/servo_gfx.rc | 4 + 6 files changed, 552 insertions(+) create mode 100644 src/servo-gfx/platform/android/font.rs create mode 100644 src/servo-gfx/platform/android/font_context.rs create mode 100644 src/servo-gfx/platform/android/font_list.rs diff --git a/src/servo-gfx/font.rs b/src/servo-gfx/font.rs index 9178d69351fd..62613c09fc6c 100644 --- a/src/servo-gfx/font.rs +++ b/src/servo-gfx/font.rs @@ -312,6 +312,13 @@ pub impl Font { let size = self.style.pt_size as AzFloat; ScaledFont::new(self.backend, freetype_font, size) } + + #[cfg(target_os="android")] + priv fn create_azure_font(&self) -> ScaledFont { + let freetype_font = self.handle.face; + let size = self.style.pt_size as AzFloat; + ScaledFont::new(self.backend, freetype_font, size) + } } diff --git a/src/servo-gfx/platform/android/font.rs b/src/servo-gfx/platform/android/font.rs new file mode 100644 index 000000000000..288a2cf5876c --- /dev/null +++ b/src/servo-gfx/platform/android/font.rs @@ -0,0 +1,297 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern mod freetype; + +use font::{CSSFontWeight, FontHandleMethods, FontMetrics, FontTableMethods}; +use font::{FontTableTag, FractionalPixel, SpecifiedFontStyle, UsedFontStyle, FontWeight100}; +use font::{FontWeight200, FontWeight300, FontWeight400, FontWeight500, FontWeight600}; +use font::{FontWeight700, FontWeight800, FontWeight900}; +use geometry::Au; +use geometry; +use platform::font_context::FontContextHandle; +use text::glyph::GlyphIndex; +use text::util::{float_to_fixed, fixed_to_float}; + +use freetype::freetype::bindgen::{FT_Get_Char_Index, FT_Get_Postscript_Name}; +use freetype::freetype::bindgen::{FT_Load_Glyph, FT_Set_Char_Size}; +use freetype::freetype::bindgen::{FT_New_Face, FT_Get_Sfnt_Table}; +use freetype::freetype::bindgen::{FT_New_Memory_Face, FT_Done_Face}; +use freetype::freetype::{FTErrorMethods, FT_F26Dot6, FT_Face, FT_FaceRec}; +use freetype::freetype::{FT_GlyphSlot, FT_Library, FT_Long, FT_ULong}; +use freetype::freetype::{FT_STYLE_FLAG_ITALIC, FT_STYLE_FLAG_BOLD}; +use freetype::freetype::{FT_SizeRec, FT_UInt, FT_Size_Metrics}; +use freetype::freetype::{ft_sfnt_os2}; +use freetype::tt_os2::TT_OS2; + +fn float_to_fixed_ft(f: float) -> i32 { + float_to_fixed(6, f) +} + +fn fixed_to_float_ft(f: i32) -> float { + fixed_to_float(6, f) +} + +pub struct FontTable { + bogus: () +} + +impl FontTableMethods for FontTable { + fn with_buffer(&self, _blk: &fn(*u8, uint)) { + fail!() + } +} + +enum FontSource { + FontSourceMem(~[u8]), + FontSourceFile(~str) +} + +pub struct FontHandle { + // The font binary. This must stay valid for the lifetime of the font, + // if the font is created using FT_Memory_Face. + source: FontSource, + face: FT_Face, +} + +#[unsafe_destructor] +impl Drop for FontHandle { + fn finalize(&self) { + assert!(self.face.is_not_null()); + if !FT_Done_Face(self.face).succeeded() { + fail!(~"FT_Done_Face failed"); + } + } +} + +impl FontHandleMethods for FontHandle { + pub fn new_from_buffer(fctx: &FontContextHandle, + buf: ~[u8], + style: &SpecifiedFontStyle) + -> Result { + let ft_ctx: FT_Library = fctx.ctx.ctx; + if ft_ctx.is_null() { return Err(()); } + + let face_result = do vec::as_imm_buf(buf) |bytes: *u8, len: uint| { + create_face_from_buffer(ft_ctx, bytes, len, style.pt_size) + }; + + // TODO: this could be more simply written as result::chain + // and moving buf into the struct ctor, but cant' move out of + // captured binding. + return match face_result { + Ok(face) => Ok(FontHandle { face: face, source: FontSourceMem(buf) }), + Err(()) => Err(()) + }; + + fn create_face_from_buffer(lib: FT_Library, + cbuf: *u8, cbuflen: uint, pt_size: float) + -> Result { + + let mut face: FT_Face = ptr::null(); + let face_index = 0 as FT_Long; + let result = FT_New_Memory_Face(lib, cbuf, cbuflen as FT_Long, + face_index, ptr::to_unsafe_ptr(&face)); + + if !result.succeeded() || face.is_null() { + return Err(()); + } + if FontHandle::set_char_size(face, pt_size).is_ok() { + Ok(face) + } else { + Err(()) + } + } + } + + // an identifier usable by FontContextHandle to recreate this FontHandle. + fn face_identifier(&self) -> ~str { + /* FT_Get_Postscript_Name seems like a better choice here, but it + doesn't give usable results for fontconfig when deserializing. */ + unsafe { str::raw::from_c_str((*self.face).family_name) } + } + fn family_name(&self) -> ~str { + unsafe { str::raw::from_c_str((*self.face).family_name) } + } + fn face_name(&self) -> ~str { + unsafe { str::raw::from_c_str(FT_Get_Postscript_Name(self.face)) } + } + fn is_italic(&self) -> bool { + unsafe { (*self.face).style_flags & FT_STYLE_FLAG_ITALIC != 0 } + } + fn boldness(&self) -> CSSFontWeight { + let default_weight = FontWeight400; + if unsafe { (*self.face).style_flags & FT_STYLE_FLAG_BOLD == 0 } { + default_weight + } else { + let os2 = FT_Get_Sfnt_Table(self.face, ft_sfnt_os2) as *TT_OS2; + let valid = os2.is_not_null() && unsafe { (*os2).version != 0xffff }; + if valid { + let weight = unsafe { (*os2).usWeightClass }; + match weight { + 1 | 100..199 => FontWeight100, + 2 | 200..299 => FontWeight200, + 3 | 300..399 => FontWeight300, + 4 | 400..499 => FontWeight400, + 5 | 500..599 => FontWeight500, + 6 | 600..699 => FontWeight600, + 7 | 700..799 => FontWeight700, + 8 | 800..899 => FontWeight800, + 9 | 900..999 => FontWeight900, + _ => default_weight + } + } else { + default_weight + } + } + } + + fn clone_with_style(&self, + fctx: &FontContextHandle, + style: &UsedFontStyle) -> Result { + match self.source { + FontSourceMem(ref buf) => { + FontHandleMethods::new_from_buffer(fctx, buf.clone(), style) + } + FontSourceFile(copy file) => { + FontHandle::new_from_file(fctx, file, style) + } + } + } + + pub fn glyph_index(&self, + codepoint: char) -> Option { + assert!(self.face.is_not_null()); + let idx = FT_Get_Char_Index(self.face, codepoint as FT_ULong); + return if idx != 0 as FT_UInt { + Some(idx as GlyphIndex) + } else { + debug!("Invalid codepoint: %?", codepoint); + None + }; + } + + pub fn glyph_h_advance(&self, + glyph: GlyphIndex) -> Option { + assert!(self.face.is_not_null()); + let res = FT_Load_Glyph(self.face, glyph as FT_UInt, 0); + if res.succeeded() { + unsafe { + let void_glyph = (*self.face).glyph; + let slot: FT_GlyphSlot = cast::transmute(void_glyph); + assert!(slot.is_not_null()); + debug!("metrics: %?", (*slot).metrics); + let advance = (*slot).metrics.horiAdvance; + debug!("h_advance for %? is %?", glyph, advance); + let advance = advance as i32; + return Some(fixed_to_float_ft(advance) as FractionalPixel); + } + } else { + debug!("Unable to load glyph %?. reason: %?", glyph, res); + return None; + } + } + + pub fn get_metrics(&self) -> FontMetrics { + /* TODO(Issue #76): complete me */ + let face = self.get_face_rec(); + + let underline_size = self.font_units_to_au(face.underline_thickness as float); + let underline_offset = self.font_units_to_au(face.underline_position as float); + let em_size = self.font_units_to_au(face.units_per_EM as float); + let ascent = self.font_units_to_au(face.ascender as float); + let descent = self.font_units_to_au(face.descender as float); + let max_advance = self.font_units_to_au(face.max_advance_width as float); + + return FontMetrics { + underline_size: underline_size, + underline_offset: underline_offset, + leading: geometry::from_pt(0.0), //FIXME + x_height: geometry::from_pt(0.0), //FIXME + em_size: em_size, + ascent: ascent, + descent: descent, + max_advance: max_advance + } + } + + fn get_table_for_tag(&self, _: FontTableTag) -> Option { + None + } +} + +pub impl<'self> FontHandle { + priv fn set_char_size(face: FT_Face, pt_size: float) -> Result<(), ()>{ + let char_width = float_to_fixed_ft(pt_size) as FT_F26Dot6; + let char_height = float_to_fixed_ft(pt_size) as FT_F26Dot6; + let h_dpi = 72; + let v_dpi = 72; + + let result = FT_Set_Char_Size(face, char_width, char_height, h_dpi, v_dpi); + if result.succeeded() { Ok(()) } else { Err(()) } + } + + pub fn new_from_file(fctx: &FontContextHandle, file: ~str, + style: &SpecifiedFontStyle) -> Result { + let ft_ctx: FT_Library = fctx.ctx.ctx; + if ft_ctx.is_null() { return Err(()); } + + let mut face: FT_Face = ptr::null(); + let face_index = 0 as FT_Long; + do str::as_c_str(file) |file_str| { + FT_New_Face(ft_ctx, file_str, + face_index, ptr::to_unsafe_ptr(&face)); + } + if face.is_null() { + return Err(()); + } + if FontHandle::set_char_size(face, style.pt_size).is_ok() { + Ok(FontHandle { source: FontSourceFile(file), face: face }) + } else { + Err(()) + } + } + + pub fn new_from_file_unstyled(fctx: &FontContextHandle, file: ~str) + -> Result { + let ft_ctx: FT_Library = fctx.ctx.ctx; + if ft_ctx.is_null() { return Err(()); } + + let mut face: FT_Face = ptr::null(); + let face_index = 0 as FT_Long; + do str::as_c_str(file) |file_str| { + FT_New_Face(ft_ctx, file_str, + face_index, ptr::to_unsafe_ptr(&face)); + } + if face.is_null() { + return Err(()); + } + + Ok(FontHandle { source: FontSourceFile(file), face: face }) + } + + priv fn get_face_rec(&'self self) -> &'self FT_FaceRec { + unsafe { + &(*self.face) + } + } + + priv fn font_units_to_au(&self, value: float) -> Au { + let face = self.get_face_rec(); + + // face.size is a *c_void in the bindings, presumably to avoid + // recursive structural types + let size: &FT_SizeRec = unsafe { cast::transmute(&(*face.size)) }; + let metrics: &FT_Size_Metrics = &(*size).metrics; + + let em_size = face.units_per_EM as float; + let x_scale = (metrics.x_ppem as float) / em_size as float; + + // If this isn't true then we're scaling one of the axes wrong + assert!(metrics.x_ppem == metrics.y_ppem); + + return geometry::from_frac_px(value * x_scale); + } +} + diff --git a/src/servo-gfx/platform/android/font_context.rs b/src/servo-gfx/platform/android/font_context.rs new file mode 100644 index 000000000000..4797dddba1ad --- /dev/null +++ b/src/servo-gfx/platform/android/font_context.rs @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use font::UsedFontStyle; +use platform::font::FontHandle; +use font_context::FontContextHandleMethods; +use platform::font_list::path_from_identifier; + +use freetype::freetype::{FTErrorMethods, FT_Library}; +use freetype::freetype::bindgen::{FT_Done_FreeType, FT_Init_FreeType}; + + +struct FreeTypeLibraryHandle { + ctx: FT_Library, +} + +impl Drop for FreeTypeLibraryHandle { + fn finalize(&self) { + assert!(self.ctx.is_not_null()); + FT_Done_FreeType(self.ctx); + } +} + +pub struct FontContextHandle { + ctx: @FreeTypeLibraryHandle, +} + +pub impl FontContextHandle { + pub fn new() -> FontContextHandle { + let ctx: FT_Library = ptr::null(); + let result = FT_Init_FreeType(ptr::to_unsafe_ptr(&ctx)); + if !result.succeeded() { fail!(); } + + FontContextHandle { + ctx: @FreeTypeLibraryHandle { ctx: ctx }, + } + } +} + +impl FontContextHandleMethods for FontContextHandle { + fn clone(&self) -> FontContextHandle { + FontContextHandle { ctx: self.ctx } + } + + fn create_font_from_identifier(&self, name: ~str, style: UsedFontStyle) + -> Result { + debug!("Creating font handle for %s", name); + do path_from_identifier(name, &style).chain |file_name| { + debug!("Opening font face %s", file_name); + FontHandle::new_from_file(self, file_name, &style) + } + } +} + diff --git a/src/servo-gfx/platform/android/font_list.rs b/src/servo-gfx/platform/android/font_list.rs new file mode 100644 index 000000000000..f88df3043918 --- /dev/null +++ b/src/servo-gfx/platform/android/font_list.rs @@ -0,0 +1,182 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern mod freetype; +extern mod fontconfig; + +use fontconfig::fontconfig::{ + FcChar8, FcResultMatch, FcSetSystem, + FcResultNoMatch, FcMatchPattern, FC_SLANT_ITALIC, FC_WEIGHT_BOLD +}; +use fontconfig::fontconfig::bindgen::{ + FcConfigGetCurrent, FcConfigGetFonts, FcPatternGetString, + FcPatternDestroy, FcFontSetDestroy, FcConfigSubstitute, + FcDefaultSubstitute, FcPatternCreate, FcPatternAddString, FcPatternAddInteger, + FcFontMatch, FcFontSetList, FcObjectSetCreate, FcObjectSetDestroy, + FcObjectSetAdd, FcPatternGetInteger +}; + + +use font::{FontHandleMethods, UsedFontStyle}; +use font_context::FontContextHandleMethods; +use font_list::{FontEntry, FontFamily, FontFamilyMap}; +use platform::font::FontHandle; +use platform::font_context::FontContextHandle; + +use core::hashmap::HashMap; +use core::libc::c_int; +use core::ptr::Ptr; + +pub struct FontListHandle { + fctx: FontContextHandle, +} + +pub impl FontListHandle { + pub fn new(fctx: &FontContextHandle) -> FontListHandle { + FontListHandle { fctx: fctx.clone() } + } + + fn get_available_families(&self) -> FontFamilyMap { + let mut family_map : FontFamilyMap = HashMap::new(); + unsafe { + let config = FcConfigGetCurrent(); + let fontSet = FcConfigGetFonts(config, FcSetSystem); + for uint::range(0, (*fontSet).nfont as uint) |i| { + let font = (*fontSet).fonts.offset(i); + let family: *FcChar8 = ptr::null(); + let mut v: c_int = 0; + do str::as_c_str("family") |FC_FAMILY| { + while FcPatternGetString(*font, FC_FAMILY, v, &family) == FcResultMatch { + let family_name = str::raw::from_buf(family as *u8); + debug!("Creating new FontFamily for family: %s", family_name); + let new_family = @mut FontFamily::new(family_name); + family_map.insert(family_name, new_family); + v += 1; + } + } + } + } + return family_map; + } + + fn load_variations_for_family(&self, family: @mut FontFamily) { + debug!("getting variations for %?", family); + let config = FcConfigGetCurrent(); + let font_set = FcConfigGetFonts(config, FcSetSystem); + let font_set_array_ptr = ptr::to_unsafe_ptr(&font_set); + unsafe { + do str::as_c_str("family") |FC_FAMILY| { + do str::as_c_str(family.family_name) |family_name| { + let pattern = FcPatternCreate(); + assert!(pattern.is_not_null()); + let family_name = family_name as *FcChar8; + let ok = FcPatternAddString(pattern, FC_FAMILY, family_name); + assert!(ok != 0); + + let object_set = FcObjectSetCreate(); + assert!(object_set.is_not_null()); + + str::as_c_str("file", |FC_FILE| FcObjectSetAdd(object_set, FC_FILE) ); + str::as_c_str("index", |FC_INDEX| FcObjectSetAdd(object_set, FC_INDEX) ); + + let matches = FcFontSetList(config, font_set_array_ptr, 1, pattern, object_set); + + debug!("found %? variations", (*matches).nfont); + + for uint::range(0, (*matches).nfont as uint) |i| { + let font = (*matches).fonts.offset(i); + let file = do str::as_c_str("file") |FC_FILE| { + let file: *FcChar8 = ptr::null(); + if FcPatternGetString(*font, FC_FILE, 0, &file) == FcResultMatch { + str::raw::from_c_str(file as *libc::c_char) + } else { + fail!(); + } + }; + let index = do str::as_c_str("index") |FC_INDEX| { + let index: libc::c_int = 0; + if FcPatternGetInteger(*font, FC_INDEX, 0, &index) == FcResultMatch { + index + } else { + fail!(); + } + }; + + debug!("variation file: %?", file); + debug!("variation index: %?", index); + + let font_handle = FontHandle::new_from_file_unstyled(&self.fctx, + file); + let font_handle = font_handle.unwrap(); + + debug!("Creating new FontEntry for face: %s", font_handle.face_name()); + let entry = @FontEntry::new(family, font_handle); + family.entries.push(entry); + } + + FcFontSetDestroy(matches); + FcPatternDestroy(pattern); + FcObjectSetDestroy(object_set); + } + } + } + } +} + +pub fn path_from_identifier(name: ~str, style: &UsedFontStyle) -> Result<~str, ()> { + unsafe { + let config = FcConfigGetCurrent(); + let pattern = FcPatternCreate(); + let res = do str::as_c_str("family") |FC_FAMILY| { + do str::as_c_str(name) |family| { + FcPatternAddString(pattern, FC_FAMILY, family as *FcChar8) + } + }; + if res != 1 { + debug!("adding family to pattern failed"); + return Err(()); + } + + if style.italic { + let res = do str::as_c_str("slant") |FC_SLANT| { + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC) + }; + if res != 1 { + debug!("adding slant to pattern failed"); + return Err(()); + } + } + if style.weight.is_bold() { + let res = do str::as_c_str("weight") |FC_WEIGHT| { + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD) + }; + if res != 1 { + debug!("adding weight to pattern failed"); + return Err(()); + } + } + + if FcConfigSubstitute(config, pattern, FcMatchPattern) != 1 { + debug!("substitution failed"); + return Err(()); + } + FcDefaultSubstitute(pattern); + let result = FcResultNoMatch; + let result_pattern = FcFontMatch(config, pattern, &result); + if result != FcResultMatch && result_pattern.is_null() { + debug!("obtaining match to pattern failed"); + return Err(()); + } + + let file: *FcChar8 = ptr::null(); + let res = do str::as_c_str("file") |FC_FILE| { + FcPatternGetString(result_pattern, FC_FILE, 0, &file) + }; + if res != FcResultMatch { + debug!("getting filename for font failed"); + return Err(()); + } + Ok(str::raw::from_buf(file as *u8)) + } +} diff --git a/src/servo-gfx/platform/mod.rs b/src/servo-gfx/platform/mod.rs index 2d8fb4f9ce75..34870cc2f2d4 100644 --- a/src/servo-gfx/platform/mod.rs +++ b/src/servo-gfx/platform/mod.rs @@ -4,6 +4,7 @@ #[cfg(target_os="linux")] pub use platform::linux::{font, font_context, font_list}; #[cfg(target_os="macos")] pub use platform::macos::{font, font_context, font_list}; +#[cfg(target_os="android")] pub use platform::android::{font, font_context, font_list}; #[cfg(target_os="linux")] pub mod linux { @@ -19,3 +20,9 @@ pub mod macos { pub mod font_list; } +#[cfg(target_os="android")] +pub mod android { + pub mod font; + pub mod font_context; + pub mod font_list; +} diff --git a/src/servo-gfx/servo_gfx.rc b/src/servo-gfx/servo_gfx.rc index f785e0fc8775..4d47e8b7cbdf 100644 --- a/src/servo-gfx/servo_gfx.rc +++ b/src/servo-gfx/servo_gfx.rc @@ -29,6 +29,10 @@ extern mod harfbuzz; #[cfg(target_os="macos")] extern mod core_graphics; #[cfg(target_os="macos")] extern mod core_text; +// android-specific library dependencies +#[cfg(target_os="android")] extern mod fontconfig; +#[cfg(target_os="android")] extern mod freetype; + pub use gfx_font = font; pub use gfx_font_context = font_context; pub use gfx_font_list = font_list;