├── .gitignore ├── .gitmodules ├── .travis.yml ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── package.json ├── python2 └── python2 ├── scripts └── harfbuzz-js.sh └── src ├── main ├── coffee │ ├── connector.coffee │ └── hb-js.coffee └── emscripten │ └── exports.txt └── test ├── coffee ├── connectorTest.coffee └── harfbuzzTest.coffee ├── js ├── harfbuzz-test-post.js └── harfbuzz-test-pre.js └── resources └── OpenBaskerville-0.0.75.otf /.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.log 6 | temp 7 | 8 | .idea 9 | *.iml 10 | *.ipr 11 | *.iws 12 | 13 | .gradle 14 | build/ 15 | 16 | node_modules/ 17 | 18 | emsdk/* 19 | !emsdk/emsdk 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "harfbuzz"] 2 | path = harfbuzz 3 | url = https://github.com/behdad/harfbuzz.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | env: 5 | global: 6 | - TERM=dumb 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HarfBuzz JS 2 | =========== 3 | 4 | JavaScript port of the [HarfBuzz OpenType text shaping engine](https://github.com/behdad/harfbuzz). 5 | It uses [Emscripten](https://github.com/kripken/emscripten) to compile the code into JavaScript. 6 | 7 | ## How to build 8 | 9 | You will need [Ragel](http://www.complang.org/ragel/) installed, as HarfBuzz is using it. 10 | Other tools will be installed automatially. 11 | 12 | ./gradlew assemble 13 | 14 | **Note:** This only works on a Mac (and probably Windows), as Emscripten doesn't publish pre-built binaries for Linux. 15 | 16 | ## Running tests 17 | 18 | ./gradlew check 19 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group "org.harfbuzz" 2 | 3 | apply plugin: "maven" 4 | 5 | String gitVersion 6 | if (hasProperty("release")) { 7 | gitVersion = [ "git", "describe", "--match", "[0-9]*", "--dirty"].execute().text.trim() 8 | } else { 9 | gitVersion = [ "git", "describe", "--match", "[0-9]*", "--abbrev=0"].execute().text.trim() + "-SNAPSHOT" 10 | } 11 | 12 | def harfbuzzDir = file("harfbuzz") 13 | def harfbuzzVersion = [ "git", "describe", "--abbrev=0" ].execute(null, harfbuzzDir).text.trim() 14 | version = harfbuzzVersion + "-" + gitVersion 15 | 16 | task wrapper(type: Wrapper) { 17 | gradleVersion = "2.0" 18 | } 19 | 20 | def emscriptenVersion = "1.22.0" 21 | def emscriptenDir = file("emsdk/emscripten/${emscriptenVersion}") 22 | def nodeVersion = "0.10.18_64bit" 23 | def nodeDir = file("emsdk/node/${nodeVersion}/bin") 24 | 25 | task cleanHarfBuzz << { 26 | if (file("${harfbuzzDir}/Makefile").exists()) { 27 | def proc = "make clean".execute(null, harfbuzzDir) 28 | proc.waitForProcessOutput(System.out, System.err) 29 | if (proc.exitValue()) { 30 | throw new RuntimeException("HarfBuzz clean failed") 31 | } 32 | } 33 | delete "config.h" 34 | } 35 | 36 | clean { 37 | dependsOn cleanHarfBuzz 38 | } 39 | 40 | task installEmscriptenSdk(type: Exec) { 41 | commandLine "./install-emsdk.sh", "sdk-${emscriptenVersion}-64bit" 42 | } 43 | 44 | task installNpmPackages(type:Exec) { 45 | dependsOn installEmscriptenSdk 46 | inputs.file "package.json" 47 | commandLine "${nodeDir}/npm", "install" 48 | } 49 | 50 | task compileJsConnector(type: CompileCoffee) { 51 | dependsOn installNpmPackages 52 | nodePath = nodeDir 53 | srcDir = file("src/main/coffee") 54 | outputDir = file("${project.buildDir}/compiled-coffee") 55 | } 56 | 57 | task generateUcdnJs { 58 | def input = file("${harfbuzzDir}/src/hb-ucdn/ucdn.h") 59 | def output = file("${project.buildDir}/ucdn.js") 60 | ext.dest = output 61 | inputs.file input 62 | outputs.file output 63 | 64 | doLast { 65 | output.parentFile.mkdirs() 66 | output.delete() 67 | output.withWriter { writer -> 68 | input.withReader { reader -> 69 | while (true) { 70 | def line = reader.readLine() 71 | if (line == null) break 72 | def matches = line =~ /#define (UCDN_[A-Z0-9_]+) (\d+)/ 73 | if (matches) { 74 | def name = matches[0][1] 75 | def value = matches[0][2] 76 | writer.println "Module[\"${name}\"] = ${value};" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | task combineJsConnector { 85 | dependsOn compileJsConnector 86 | dependsOn generateUcdnJs 87 | def output = file("${buildDir}/post.js") 88 | outputs.file output 89 | ext.dest = output 90 | inputs.dir compileJsConnector.outputDir 91 | inputs.file generateUcdnJs.dest 92 | 93 | doLast { 94 | delete(output) 95 | output << file("${compileJsConnector.outputDir}/connector.js").text 96 | output << file("${compileJsConnector.outputDir}/hb-js.js").text 97 | output << generateUcdnJs.dest.text 98 | } 99 | } 100 | 101 | task configureHarfbuzz(type: Exec) { 102 | dependsOn installEmscriptenSdk 103 | 104 | inputs.file "${harfbuzzDir}/configure.ac" 105 | outputs.file "${harfbuzzDir}/config.h" 106 | outputs.file "${harfbuzzDir}/Makefile" 107 | 108 | environment PATH: "${projectDir}/python2" + File.pathSeparator + System.getenv().get("PATH") 109 | 110 | commandLine "${emscriptenDir}/emconfigure", "./autogen.sh", 111 | "--with-glib=no", 112 | "--with-gobject=no", 113 | "--with-cairo=no", 114 | "--with-icu=no", 115 | "--with-graphite2=no", 116 | "--with-freetype=no", 117 | "--with-uniscribe=no", 118 | "--with-coretext=no", 119 | // We need to do this to build shared libs 120 | "--host", "i386-linux" 121 | workingDir harfbuzzDir 122 | 123 | doLast { 124 | for (outputFile in outputs.getFiles()) { 125 | if (!outputFile.exists()) { 126 | throw new RuntimeException("Emscripten configuration didn't create required file: " + outputFile) 127 | } 128 | } 129 | } 130 | } 131 | 132 | task makeHarfbuzz(type: Exec) { 133 | dependsOn configureHarfbuzz 134 | 135 | environment PATH: "${projectDir}/python2" + File.pathSeparator + System.getenv().get("PATH") 136 | 137 | commandLine "${emscriptenDir}/emmake", "make", "all-recursive" 138 | workingDir harfbuzzDir 139 | } 140 | 141 | def EXPORTED_FUNCTIONS = [] 142 | file("src/main/emscripten/exports.txt").eachLine { line -> 143 | if (line.startsWith("#") || !line.trim()) { 144 | return; 145 | } 146 | EXPORTED_FUNCTIONS += "_${line}" 147 | } 148 | 149 | def DEAD_FUNCTIONS = [ 150 | '_mprotect', 151 | ] 152 | 153 | task compileEmccProduction(type: CompileEmcc) { 154 | dependsOn combineJsConnector 155 | dependsOn makeHarfbuzz 156 | 157 | type = "" 158 | asmJs = true 159 | closure = false 160 | typedArraySupport = 2 161 | } 162 | 163 | task compileEmccDebug(type: CompileEmcc) { 164 | dependsOn combineJsConnector 165 | dependsOn makeHarfbuzz 166 | 167 | type = "-debug" 168 | asmJs = true 169 | debug = true 170 | closure = false 171 | typedArraySupport = 2 172 | } 173 | 174 | tasks.withType(CompileEmcc) { 175 | emccPath = "${emscriptenDir}/emcc" 176 | source "${harfbuzzDir}/src/.libs/libharfbuzz.so" 177 | source "${harfbuzzDir}/src/hb-ucdn/.libs/libhb-ucdn.a" 178 | postJs = combineJsConnector.dest 179 | allowMemoryGrowth = true 180 | memoryInitFile = false 181 | reservedFunctionPointers = 1024 182 | totalMemory = 64 * 1024 * 1024 183 | optimizationLevel = 2 184 | exportedFunctions = EXPORTED_FUNCTIONS 185 | deadFunctions = DEAD_FUNCTIONS 186 | outputFile = file("${buildDir}/emcc/harfbuzz${type}.js") 187 | } 188 | 189 | task combineJsForTest { 190 | dependsOn compileEmccDebug 191 | def output = file("${buildDir}/harfbuzz-test.js"); 192 | outputs.file output 193 | ext.dest = output 194 | inputs.file compileEmccDebug.outputFile 195 | 196 | doLast { 197 | delete(output) 198 | output << file("src/test/js/harfbuzz-test-pre.js").text 199 | output << compileEmccDebug.outputFile.text 200 | output << file("src/test/js/harfbuzz-test-post.js").text 201 | } 202 | } 203 | 204 | task testJs(type:Exec) { 205 | dependsOn installNpmPackages 206 | dependsOn combineJsForTest 207 | 208 | environment PATH: "${nodeDir}" + File.pathSeparator + System.getenv().get("PATH") 209 | 210 | commandLine "node_modules/mocha/bin/mocha", "--compilers", "coffee:coffee-script/register", "-R", "spec", 211 | "src/test/coffee/*.coffee" 212 | } 213 | 214 | task test { 215 | dependsOn testJs 216 | } 217 | 218 | task check { 219 | dependsOn test 220 | } 221 | 222 | task build { 223 | dependsOn assemble, check 224 | } 225 | 226 | artifacts { 227 | archives(compileEmccProduction.outputFile) { 228 | name project.name 229 | type "js" 230 | builtBy compileEmccProduction 231 | } 232 | archives(compileEmccDebug.outputFile) { 233 | name project.name 234 | type "js" 235 | classifier "debug" 236 | builtBy compileEmccDebug 237 | } 238 | } 239 | 240 | uploadArchives { 241 | dependsOn check 242 | repositories { 243 | if (hasProperty("nexusUser") && hasProperty("nexusPassword")) { 244 | ivy { 245 | if (hasProperty("release")) { 246 | url "https://artifactory.prezi.com/prezi-client-release-local" 247 | } else { 248 | url "https://artifactory.prezi.com/prezi-client-snapshot-local" 249 | } 250 | layout "maven" 251 | credentials { 252 | username = property("nexusUser") 253 | password = property("nexusPassword") 254 | } 255 | } 256 | } 257 | } 258 | } 259 | 260 | class CompileEmcc extends SourceTask { 261 | @OutputFile 262 | File outputFile 263 | 264 | String emccPath 265 | 266 | @Optional 267 | @InputFile 268 | File preJs 269 | 270 | @Optional 271 | @InputFile 272 | File postJs 273 | 274 | @Input 275 | String type = "" 276 | 277 | @Input 278 | List exportedFunctions = new ArrayList() 279 | 280 | @Input 281 | List deadFunctions = new ArrayList() 282 | 283 | @Input 284 | int reservedFunctionPointers = 0 285 | 286 | @Input 287 | int totalMemory = 16 * 1024 * 1024 288 | 289 | @Input 290 | int optimizationLevel = 0 291 | 292 | @Input 293 | boolean closure = false 294 | 295 | @Input 296 | boolean allowMemoryGrowth = true 297 | 298 | @Input 299 | int typedArraySupport = 2 300 | 301 | @Input 302 | boolean debug = false 303 | 304 | @Input 305 | boolean memoryInitFile = false 306 | 307 | @Input 308 | boolean asmJs = false 309 | 310 | 311 | @TaskAction 312 | void doCompile() { 313 | def emccVersion = "${emccPath} --version | perl -pe 's/emcc \\(Emscripten GCC-like replacement\\) (\\d+(?:\\.\\d+)*) \\(commit ([0-9a-f]+)\\)/\$1-\$2/'".execute().text.split("\n")[0].trim() 314 | logger.info "Compiling with ${emccVersion}" 315 | 316 | def versionHeader = project.file("${project.buildDir}/version-header.js") 317 | versionHeader.delete() 318 | versionHeader.createNewFile() 319 | versionHeader << "Module['version'] = '${project.version}${type}';\n"; 320 | versionHeader << "Module['emccVersion'] = '${emccVersion}';\n"; 321 | 322 | def commandLine = [ emccPath ] 323 | commandLine.addAll source.files 324 | 325 | if (debug) commandLine.add "-g" 326 | 327 | if (preJs != null) commandLine.addAll "--pre-js", preJs 328 | if (postJs != null) commandLine.addAll "--post-js", postJs 329 | commandLine.addAll "--post-js", versionHeader 330 | 331 | 332 | commandLine.addAll "-o", outputFile 333 | 334 | commandLine.add "-O" + optimizationLevel 335 | 336 | commandLine.addAll "--closure", (closure ? 1 : 0) 337 | 338 | commandLine.addAll "--memory-init-file", (memoryInitFile ? 1 : 0) 339 | 340 | commandLine.addAll "-s", "ALLOW_MEMORY_GROWTH=" + (allowMemoryGrowth ? 1 : 0) 341 | 342 | commandLine.addAll "-s", "USE_TYPED_ARRAYS=" + typedArraySupport 343 | 344 | commandLine.addAll "-s", "ASM_JS=" + (asmJs ? 1 : 0) 345 | 346 | commandLine.addAll "-s", "RESERVED_FUNCTION_POINTERS=" + reservedFunctionPointers 347 | 348 | commandLine.addAll "-s", "TOTAL_MEMORY=" + totalMemory 349 | 350 | if (exportedFunctions != null && exportedFunctions.size() > 0) { 351 | commandLine.addAll "-s", "EXPORTED_FUNCTIONS=['" + exportedFunctions.join("','") + "']"; 352 | } 353 | 354 | if (deadFunctions != null && deadFunctions.size() > 0) { 355 | commandLine.addAll "-s", "DEAD_FUNCTIONS=['" + deadFunctions.join("','") + "']"; 356 | } 357 | 358 | logger.info("Executing: ${commandLine.join(" ")}") 359 | 360 | def proc = commandLine.execute( 361 | [ 362 | "PATH=${project.projectDir}/python2" + File.pathSeparator + System.getenv().get("PATH") 363 | ], 364 | project.projectDir 365 | ) 366 | proc.waitForProcessOutput(System.out, System.err) 367 | 368 | if (proc.exitValue()) { 369 | throw new RuntimeException("Emscripten build failed") 370 | } 371 | } 372 | } 373 | 374 | public class CompileCoffee extends DefaultTask 375 | { 376 | def srcDir = "src/coffee" 377 | 378 | def outputDir = "${project.buildDir}/compiled-coffee" 379 | 380 | File nodePath 381 | 382 | @InputDirectory 383 | File getSrcDir() { project.file(srcDir) } 384 | def setSrcDir(File srcDir) { this.srcDir = srcDir } 385 | 386 | @OutputDirectory 387 | File getOutputDir() { project.file(outputDir) } 388 | def setOutputDir(File outputDir) { this.outputDir = outputDir } 389 | 390 | @TaskAction 391 | void doCompile() { 392 | logger.info "Compiling CoffeeScript sources from $srcDir into $outputDir" 393 | 394 | def outputDirFile = getOutputDir() 395 | // Recursively delete output directory if it exists 396 | outputDirFile.deleteDir() 397 | 398 | def tree = project.fileTree srcDir, { 399 | include '**/*.coffee' 400 | } 401 | 402 | tree.visit { visit -> 403 | if (visit.directory) return 404 | 405 | def inputFile = visit.file 406 | def inputPath = visit.path 407 | def outputPath = inputPath.replaceAll(/\.coffee$/, '.js') 408 | def outputFile = new File(outputDirFile, outputPath) 409 | 410 | logger.info "Compiling ${inputPath} to ${outputFile.absolutePath}" 411 | 412 | outputFile.parentFile.mkdirs() 413 | 414 | FileOutputStream fos = new FileOutputStream(outputFile) 415 | 416 | def proc = ["${project.projectDir}/node_modules/.bin/coffee", "-cb", "-p", "${inputFile.absolutePath}"].execute( 417 | [ 418 | "PATH=${nodePath}" + File.pathSeparator + System.getenv().get("PATH") 419 | ], 420 | project.projectDir 421 | ) 422 | proc.consumeProcessOutput(fos, System.out) 423 | proc.waitFor() 424 | fos.close() 425 | 426 | if (proc.exitValue()) { 427 | throw new RuntimeException("CoffeeScript build failed") 428 | } 429 | } 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prezi/harfbuzz-js/735fc0b8e784f31ab82f16e481ada7e0c1669e5d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 24 02:49:46 CEST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "harfbuzz-js", 3 | "dependencies": { 4 | "chai": "1.9.1" 5 | }, 6 | "devDependencies": { 7 | "coffee-script": "1.7.1", 8 | "mocha": "1.21.4" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /python2/python2: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Alias, sort of 3 | python2.7 "$@" 4 | -------------------------------------------------------------------------------- /scripts/harfbuzz-js.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # export JAVA_HOME=/usr/lib/jvm/java-7-oracle 4 | unset JAVA_HOME 5 | 6 | ./gradlew clean uploadArchives --info --stacktrace -Prelease 7 | -------------------------------------------------------------------------------- /src/main/coffee/connector.coffee: -------------------------------------------------------------------------------- 1 | Module["free"] = free = (struct) -> 2 | _free struct["$ptr"] 3 | 4 | remapCallbacks = [] 5 | Module["registerMemoryRemapCallback"] = registerMemoryRemapCallback = (remapCallback, userData) -> 6 | # console.log "Mapping memory from #{offset}, length: #{length}, first byte: #{getValue offset, 'i8'}" 7 | callbackData = "userData": userData, "callback": remapCallback 8 | remapCallbacks.push callbackData 9 | return callbackData 10 | 11 | Module["unregisterMemoryRemapCallback"] = unregisterMemoryRemapCallback = (callbackData) -> 12 | index = remapCallbacks.indexOf callbackData 13 | remapCallbacks.splice index, 1 unless index is -1 14 | 15 | # When memory size is increased, we need to re-map our arrays 16 | __originalEnlargeMemory = enlargeMemory 17 | enlargeMemory = -> 18 | __originalEnlargeMemory() 19 | 20 | for remapCallback in remapCallbacks 21 | remapCallback["callback"] Module["HEAPU8"], remapCallback["userData"] 22 | 23 | Module["Void"] = Void = "i32" 24 | 25 | Module["SelfPtr"] = SelfPtr = {} 26 | 27 | NON_HEAP = -1 28 | INVALID_SIZE = -1 29 | 30 | class CObject 31 | constructor: (heap) -> 32 | # First give a chance for the type to resolve itself 33 | @["$type"]["resolve"]() 34 | 35 | if not heap? 36 | # console.log "Allocating #{@["$type"]["size"]} bytes for #{@["$type"].toString()}" 37 | heap = allocate @["$type"]["size"], "i8", ALLOC_NORMAL 38 | if heap != NON_HEAP 39 | # console.log "Setting $ptr to #{heap}" 40 | @["$ptr"] = heap 41 | 42 | @::["$offset"] = (index) -> 43 | if @["$ptr"] == NON_HEAP then throw new Error "Non-heap" 44 | return new @["$type"](@["$ptr"] + index * @["$type"]["size"]) 45 | 46 | @::["$next"] = -> 47 | @["$offset"] 1 48 | 49 | @::["toString"] = (stack = []) -> 50 | dumpData @, @["$type"], stack 51 | 52 | @["resolve"] = -> 53 | throw new Error("Unknown type, cannot resolve") 54 | 55 | @["writeTo"] = (address, value) -> 56 | if value["$ptr"] == NON_HEAP then throw new Error "Non heap object" 57 | if value["$ptr"] == 0 then throw new Error "Null reference" 58 | _memcpy address, value["$ptr"], @["size"] 59 | 60 | @["fromNative"] = (value) -> 61 | new @(value) 62 | 63 | @["toNative"] = (value) -> 64 | if value is null then 0 else value["$ptr"] 65 | 66 | 67 | Module["struct"] = struct = (fields) -> class CStruct extends CObject 68 | @::["$type"] = @ 69 | 70 | constructor: (heap) -> 71 | super heap 72 | 73 | @::["toString"] = (stack = []) -> 74 | "{" + (" #{field}: #{dumpData @['get'](field), type, stack}" for own field, type of fields).join(",") + " }" 75 | 76 | @["toString"] = (stack = []) -> 77 | "{" + (" #{field}: #{dumpType type, stack}" for own field, type of fields).join(",") + " }" 78 | 79 | resolved = false 80 | 81 | @["redefine"] = (newFields) -> 82 | if resolved then throw new Error "Type #{@} is already resolved" 83 | fields = newFields 84 | 85 | getters = {} 86 | setters = {} 87 | @["resolve"] = -> 88 | if resolved then return 89 | 90 | fieldsNotToResolve = [] 91 | for own field, type of fields 92 | if type is SelfPtr 93 | fields[field] = ptr(@) 94 | fieldsNotToResolve.push field 95 | 96 | # console.log "Defining fields:", fields 97 | 98 | # Define properties 99 | offset = 0 100 | for own field, type of fields 101 | # console.log "Creating property #{field} at #{offset} of type #{type} " + new Error("Trace").stack 102 | do (offset, field, type) -> 103 | if simpleType type 104 | # console.log "Creating simple property at #{offset} with type #{type}" 105 | getters[field] = (object) -> 106 | # console.log "Getting #{field} at #{object['$ptr']} + #{offset}" 107 | getValue object["$ptr"] + offset, type 108 | setters[field] = (object, value) -> 109 | # console.log "Setting #{field} to #{value} at #{object['$ptr']} + #{offset}" 110 | setValue object["$ptr"] + offset, value, type 111 | return 112 | else 113 | # console.log "Creating compound property at #{offset} with type #{type}" 114 | type["resolve"]() if fieldsNotToResolve.indexOf(field) is -1 115 | compoundProperty = null 116 | 117 | getters[field] = (object) -> 118 | # console.log "Getting compound #{field} at #{object['$ptr']} + #{offset}" 119 | if compoundProperty is null 120 | # console.log "Creating compound at #{object['ptr']} + #{offset} for #{field}" 121 | compoundProperty = new type(object["$ptr"] + offset) 122 | return compoundProperty 123 | setters[field] = (object, otherStruct) -> 124 | # console.log "Setting compound #{field} at #{object['$ptr']} + #{offset} to #{otherStruct}" 125 | if otherStruct["$type"] isnt type 126 | throw new Error "Cannot load incompatible data: #{type} vs. #{otherStruct['$type']}" 127 | writeTo object["$ptr"] + offset, otherStruct, type 128 | return 129 | offset += sizeof type 130 | 131 | @["size"] = offset 132 | 133 | resolved = true 134 | 135 | @::["get"] = (field) -> 136 | getters[field](@) 137 | 138 | @::["set"] = (field, value) -> 139 | setters[field](@, value) 140 | return 141 | 142 | 143 | Module["array"] = array = (elemType, count) -> class CArray extends CObject 144 | if not elemType? then throw new Error "Element type is not specified" 145 | if typeof count isnt "number" or count < 0 then throw new Error "Array size must be non-negative: #{count}" 146 | 147 | @::["$type"] = @ 148 | @["count"] = count 149 | @["elemType"] = elemType 150 | 151 | constructor: (heap) -> 152 | super heap 153 | 154 | checkIndex = (index) -> 155 | if not (0 <= index < count) 156 | throw new Error "Index out of bounds: 0 <= #{index} < #{count}" 157 | return index 158 | 159 | @::["get"] = (index) -> 160 | address = @["$ptr"] + checkIndex(index) * sizeofType elemType 161 | if simpleType elemType 162 | getValue address, elemType 163 | else 164 | new elemType address 165 | 166 | @::["set"] = (index, value) -> 167 | address = @["$ptr"] + checkIndex(index) * sizeofType elemType 168 | if simpleType elemType 169 | if typeof value isnt "number" 170 | throw new Error "Cannot load #{typeof value} to #{elemType}" 171 | setValue address, value, elemType 172 | else 173 | if value["$type"] isnt elemType 174 | throw new Error "Cannot load #{value["$type"]} to #{elemType}" 175 | writeTo address, value, elemType 176 | return 177 | 178 | @::["getAddress"] = -> 179 | @["ptr"](0) 180 | 181 | @::["ptr"] = (index) -> 182 | type = if simpleType elemType then simplePointerTypes[elemType] else elemType 183 | new type @["$ptr"] + checkIndex(index) * sizeofType elemType 184 | 185 | @::["toArray"] = -> 186 | @["get"](i) for i in [0...count] 187 | 188 | @::["toString"] = (stack = []) -> 189 | "[" + (" #{dumpData @get(index), elemType, stack}" for index in [0...count]).join(",") + " ]" 190 | 191 | @["toString"] = (stack = []) -> 192 | "#{dumpType elemType, stack}[#{count}]" 193 | 194 | resolved = false 195 | @["resolve"] = -> 196 | if resolved then return 197 | elemType["resolve"]() unless simpleType elemType 198 | size = count * sizeofType elemType 199 | @["size"] = size 200 | resolved = true 201 | 202 | 203 | Module["ptr"] = ptr = (targetType) -> class CPointer extends CObject 204 | if not targetType? then throw new Error "Target type is not specified" 205 | 206 | @::["$type"] = @ 207 | size = sizeof "i32" 208 | @["size"] = size 209 | 210 | _address = 0 211 | 212 | constructor: (heap = NON_HEAP, target = null) -> 213 | super heap 214 | @nonHeap = heap == NON_HEAP 215 | if @nonHeap 216 | # console.log "Non-heap pointer pointing to #{target}" 217 | _address = addressof target 218 | 219 | @::["getAddress"] = -> 220 | if @nonHeap 221 | _address 222 | else 223 | getValue(@["$ptr"], "i32") 224 | 225 | @::["setAddress"] = (targetAddress) -> 226 | if @nonHeap 227 | _address = targetAddress 228 | else 229 | setValue @["$ptr"], targetAddress, "i32" 230 | 231 | @::["get"] = -> 232 | address = @["getAddress"]() 233 | if address == 0 then null 234 | else if simpleType targetType then getValue address, targetType 235 | else new targetType address 236 | 237 | @::["set"] = (target) -> 238 | if @["getAddress"]() == 0 then throw new Error "Null reference" 239 | writeTo @["getAddress"](), target, targetType 240 | return 241 | 242 | @::["toString"] = (stack = []) -> 243 | address = @["getAddress"]() 244 | if address == 0 245 | "NULL" 246 | else 247 | "@#{address}->#{dumpData @['get'](), targetType, stack}" 248 | 249 | @["toString"] = (stack = []) -> 250 | "*#{dumpType targetType, stack}" 251 | 252 | resolved = false 253 | @["resolve"] = -> 254 | if resolved then return 255 | resolved = true 256 | targetType["resolve"]() unless simpleType targetType 257 | 258 | @["fromNative"] = (value) -> 259 | result = new @() 260 | result["setAddress"] value 261 | return result 262 | 263 | @["toNative"] = (value) -> 264 | if value is null then 0 else value["getAddress"]() 265 | 266 | 267 | Module["string"] = string = class CString extends CObject 268 | @::["$type"] = @ 269 | 270 | constructor: (arg, alloc = ALLOC_NORMAL) -> 271 | if arg is null 272 | super 0 273 | else if typeof arg is "number" 274 | super arg 275 | else if typeof arg is "string" 276 | super allocate intArrayFromString(arg), "i8", alloc 277 | else 278 | throw new Error "Cannot create a string from #{arg}" 279 | 280 | @::["getAddress"] = -> 281 | @["$ptr"] 282 | 283 | @::["toString"] = -> 284 | Pointer_stringify(@["$ptr"]) 285 | 286 | @["toString"] = -> 287 | "*char" 288 | 289 | @["resolve"] = -> 290 | 291 | @["allocate"] = (size, alloc = ALLOC_NORMAL) -> 292 | new @ allocate size, "i8", alloc 293 | 294 | 295 | Module["define"] = define = (returnType, name, argumentsDef = {}) -> 296 | returnNative = nativeTypeOf returnType 297 | argumentTypes = (type for own argument, type of argumentsDef) 298 | argumentNativeTypes = (nativeTypeOf type for type in argumentTypes) 299 | 300 | # console.log "Defining #{returnType} #{name} ( " + ("#{arg}: #{type}" for own arg, type of argumentsDef).join(", ") + " )" 301 | cFunc = cwrap name, returnNative, argumentNativeTypes 302 | 303 | Module[name] = (args...) -> 304 | nativeArgs = new Array(argumentTypes.length) 305 | # console.log "Calling '#{name}' with args:" 306 | # for i in [0...args.length] 307 | # console.log " -> ##{i}: #{args[i]}" 308 | for i in [0...argumentTypes.length] 309 | # console.log "Type #{i}: #{argumentTypes[i]}" 310 | nativeArgs[i] = toNative args[i], argumentTypes[i] 311 | # console.log "ccall '#{name}', '#{returnNative}',", argumentNativeTypes, ",", nativeArgs 312 | 313 | resultNative = cFunc.apply null, nativeArgs 314 | 315 | # console.log " native result:", resultNative 316 | result = fromNative resultNative, returnType 317 | # console.log "That is: #{result}" 318 | return result 319 | 320 | 321 | Module["callback"] = callback = (returnType, name, argumentsDef = {}, func) -> 322 | argumentTypes = (type for own argument, type of argumentsDef) 323 | 324 | callbackFunc = (nativeArgs...) -> 325 | # console.log "Calling callback #{name} with arguments:", nativeArgs 326 | # console.log " -- argument defs:", argumentsDef 327 | args = new Array(argumentTypes.length) 328 | for i in [0...argumentTypes.length] 329 | args[i] = fromNative nativeArgs[i], argumentTypes[i] 330 | 331 | result = func.apply null, args 332 | 333 | # console.log " result: #{result}" 334 | 335 | resultNative = toNative result, returnType 336 | # console.log "native result: ", resultNative 337 | return resultNative 338 | 339 | functionIndex = Runtime.addFunction callbackFunc, "x" 340 | 341 | if name? then Module[name] = functionIndex 342 | 343 | return functionIndex 344 | 345 | 346 | Module["unregisterCallback"] = unregisterCallback = (functionIndex) -> 347 | Runtime.removeFunction functionIndex 348 | 349 | 350 | Module["typedef"] = typedef = (name, type) -> 351 | # console.log "Defining #{name} = #{type}" 352 | type["typeName"] = name 353 | Module[name] = type 354 | 355 | 356 | addressof = (value, type) -> 357 | if simpleType type then throw new Error "Simple types don't have an address" 358 | if value is null then 0 else value["$ptr"] 359 | 360 | sizeof = (type) -> 361 | if simpleType type then Runtime.getNativeFieldSize type else type["size"] 362 | 363 | sizeofType = (type) -> 364 | if simpleType type then Runtime.getNativeTypeSize type else type["size"] 365 | 366 | writeTo = (address, value, type) -> 367 | if simpleType type 368 | setValue address, value, type 369 | else 370 | type["writeTo"] address, value 371 | 372 | simpleType = (type) -> 373 | typeof type is "string" 374 | 375 | nativeTypeOf = (type) -> 376 | # TODO Add string support sometime 377 | return "number" 378 | 379 | fromNative = (value, type) -> 380 | # console.log "Converting #{value} from native to #{if type then type.toString() else type}" 381 | if simpleType type then value else type["fromNative"] value 382 | 383 | toNative = (value, type) -> 384 | # console.log "Converting #{value} to native from #{if type then type.toString() else type}" 385 | if simpleType type then value else type["toNative"] value 386 | 387 | dumpType = (type, stack) -> 388 | if simpleType type 389 | s = type 390 | else if stack.indexOf(type) > -1 391 | s = if type["typeName"] then type["typeName"] else "" 392 | else 393 | if not type["typeName"]? 394 | stack.push type 395 | s = type["toString"] stack 396 | stack.pop() 397 | else 398 | s = type["typeName"] 399 | return s 400 | 401 | dumpData = (value, type, stack) -> 402 | s = null 403 | if simpleType type 404 | s = value 405 | else 406 | address = value["$ptr"] 407 | if address == 0 408 | s = "NULL" 409 | else 410 | if stack.indexOf(address) > -1 411 | s = "" 412 | else 413 | stack.push address 414 | s = value["toString"] stack 415 | stack.pop() 416 | return s 417 | 418 | simplePointerTypes = {} 419 | for type in ["i1", "i8", "i16", "i32", "i64", "float", "double"] 420 | simplePointerTypes[type] = ptr type 421 | -------------------------------------------------------------------------------- /src/main/coffee/hb-js.coffee: -------------------------------------------------------------------------------- 1 | 2 | Module["Bool"] = Bool = "i1" 3 | Module["Char"] = Char = "i8" 4 | Module["Unsigned_Char"] = Unsigned_Char = "i8" 5 | Module["int8_t"] = int8_t = "i8" 6 | Module["uint8_t"] = uint8_t = "i8" 7 | 8 | Module["Short"] = Short = "i16" 9 | Module["Unsigned_Short"] = Unsigned_Short = "i16" 10 | Module["int16_t"] = int16_t = "i16" 11 | Module["uint16_t"] = uint16_t = "i16" 12 | 13 | Module["Int"] = Int = "i32" 14 | Module["Unsigned_Int"] = Unsigned_Int = "i32" 15 | Module["int32_t"] = int32_t = "i32" 16 | Module["uint32_t"] = uint32_t = "i32" 17 | 18 | Module["Long"] = Long = "i64" 19 | Module["Unsigned_Long"] = Unsigned_Long = "i64" 20 | Module["Long_Long"] = Long_Long = "i64" 21 | Module["Unsigned_Long_Long"] = Unsigned_Long_Long = "i64" 22 | Module["int64_t"] = int64_t = "i64" 23 | Module["uint64_t"] = uint64_t = "i64" 24 | 25 | Module["Enumeration"] = Enumeration = "i32" 26 | 27 | 28 | Module["HB_TAG"] = HB_TAG = (tag) -> tag.charCodeAt(0) << 24 | tag.charCodeAt(1) << 16 | tag.charCodeAt(2) << 8 | tag.charCodeAt(3) 29 | Module["HB_UNTAG"] = HB_UNTAG = (tag) -> String.fromCharCode tag >>> 24, (tag >>> 16) & 0xFF, (tag >>> 8) & 0xFF, tag & 0xFF 30 | 31 | # hb-common.cc 32 | 33 | hb_language_impl_t = typedef "hb_language_impl_t", struct { 34 | "s": array(Char, 1); 35 | } 36 | 37 | # 38 | # hb-common.h 39 | # 40 | 41 | Module["hb_bool_t"] = hb_bool_t = Int 42 | 43 | Module["hb_codepoint_t"] = hb_codepoint_t = uint32_t 44 | Module["hb_position_t"] = hb_position_t = int32_t 45 | Module["hb_mask_t"] = hb_mask_t = uint32_t 46 | 47 | # This should be a union, but we are never going to refer to it ourselves 48 | Module["hb_var_int_t"] = hb_var_int_t = uint32_t 49 | 50 | Module["hb_tag_t"] = hb_tag_t = uint32_t 51 | 52 | Module["hb_direction_t"] = hb_direction_t = Enumeration 53 | 54 | # hb_direction_t 55 | Module["HB_DIRECTION_INVALID"] = HB_DIRECTION_INVALID = 0; 56 | Module["HB_DIRECTION_LTR"] = HB_DIRECTION_LTR = 4; 57 | Module["HB_DIRECTION_RTL"] = HB_DIRECTION_RTL = 5; 58 | Module["HB_DIRECTION_TTB"] = HB_DIRECTION_TTB = 6; 59 | Module["HB_DIRECTION_BTT"] = HB_DIRECTION_BTT = 7; 60 | 61 | Module["hb_script_t"] = hb_script_t = Enumeration 62 | 63 | hb_user_data_key_t = typedef "hb_user_data_key_t", struct { 64 | # < private > 65 | "unused": Char, 66 | } 67 | 68 | hb_destroy_func_t = ptr(Int) # void (*hb_destroy_func_t) (void *user_data) 69 | 70 | hb_language_t = ptr(hb_language_impl_t); 71 | Module["HB_LANGUAGE_INVALID"] = HB_LANGUAGE_INVALID = null 72 | 73 | # len=-1 means str is NUL-terminated 74 | define hb_language_t, "hb_language_from_string", "str": string, "len": Int 75 | define string, "hb_language_to_string", "language": hb_language_t 76 | 77 | 78 | # 79 | # hb-atomic-private.hh 80 | # 81 | 82 | hb_atomic_int_t = Int 83 | 84 | 85 | # 86 | # hb-mutex-private.hh 87 | # 88 | 89 | hb_mutex_impl_t = Int 90 | 91 | hb_mutex_t = typedef "hb_mutex_t", struct { 92 | "m": hb_mutex_impl_t, 93 | } 94 | 95 | 96 | # 97 | # hb-private.hh 98 | # 99 | 100 | hb_prealloced_array_t = (Type, StaticSize) -> typedef "hb_prealloced_array_t<#{Type}, #{StaticSize}>", struct { 101 | "len": Unsigned_Int, 102 | "allocated": Unsigned_Int, 103 | "array": ptr(Type), 104 | "static_array": array(Type, StaticSize), 105 | } 106 | 107 | hb_lockable_set_t = (item_t, lock_t) -> typedef "hb_lockable_set_t<#{item_t}, #{lock_t}>", struct { 108 | "items": hb_prealloced_array_t(item_t, 2), 109 | } 110 | 111 | 112 | # 113 | # hb-object-private.hh 114 | # 115 | 116 | hb_reference_count_t = typedef "hb_reference_count_t", struct { 117 | "ref_count": hb_atomic_int_t, 118 | } 119 | 120 | hb_user_data_item_t = typedef "hb_user_data_item_t", struct { 121 | "key": ptr(hb_user_data_key_t), 122 | "data": ptr(Int), # *void 123 | "destroy": ptr(Int), # *hb_destroy_func_t 124 | } 125 | 126 | hb_user_data_array_t = typedef "hb_user_data_array_t", struct { 127 | "items": hb_lockable_set_t(hb_user_data_item_t, hb_mutex_t), 128 | } 129 | 130 | hb_object_header_t = typedef "hb_object_header_t", struct { 131 | "ref_count": hb_reference_count_t, 132 | "mutex": hb_mutex_t, 133 | "user_data": hb_user_data_array_t, 134 | } 135 | 136 | 137 | # 138 | # hb-unicode-private.hh 139 | # 140 | 141 | HB_UNICODE_CALLBACKS = [ 142 | "combining_class", 143 | "eastasian_width", 144 | "general_category", 145 | "mirroring", 146 | "script", 147 | "compose", 148 | "decompose", 149 | "decompose_compatibility", 150 | ] 151 | 152 | hb_unicode_funcs_t = typedef "hb_unicode_funcs_t", struct { 153 | "header": hb_object_header_t, 154 | "parent": SelfPtr, # *hb_unicode_funcs_t 155 | "immutable": Bool, 156 | "func": array(ptr("i32"), HB_UNICODE_CALLBACKS.length), # This is a struct of funtion pointers 157 | "user_data": array(ptr("i32"), HB_UNICODE_CALLBACKS.length), # This is a struct of void* 158 | "destroy": array(ptr("i32"), HB_UNICODE_CALLBACKS.length), # This is a struct of funtion pointers 159 | } 160 | 161 | 162 | # 163 | # hb-unicode.hh 164 | # 165 | 166 | define hb_unicode_funcs_t, "hb_unicode_funcs_get_default" 167 | define hb_unicode_funcs_t, "hb_unicode_funcs_reference", "ufuncs": hb_unicode_funcs_t 168 | 169 | 170 | # 171 | # hb-buffer.h 172 | # 173 | 174 | hb_glyph_info_t = typedef "hb_glyph_info_t", struct { 175 | "codepoint": hb_codepoint_t, 176 | "mask": hb_mask_t, 177 | "cluster": uint32_t, 178 | 179 | # < private > 180 | "var1": hb_var_int_t, 181 | "var2": hb_var_int_t, 182 | } 183 | 184 | hb_glyph_position_t = typedef "hb_glyph_position_t", struct { 185 | "x_advance": hb_position_t, 186 | "y_advance": hb_position_t, 187 | "x_offset": hb_position_t, 188 | "y_offset": hb_position_t, 189 | 190 | # < private > 191 | "var": hb_var_int_t, 192 | } 193 | 194 | hb_buffer_content_type_t = Enumeration 195 | 196 | # 197 | # hb-buffer-private.hh 198 | # 199 | 200 | hb_segment_properties_t = typedef "hb_segment_properties_t", struct { 201 | "direction": hb_direction_t, 202 | "script": hb_script_t, 203 | "language": hb_language_t, 204 | } 205 | 206 | hb_buffer_t = typedef "hb_buffer_t", struct { 207 | "header": hb_object_header_t, 208 | "unicode": ptr(hb_unicode_funcs_t), 209 | "props": hb_segment_properties_t, 210 | "content_type": hb_buffer_content_type_t, 211 | 212 | # These three bools are packed into one Int's space 213 | #"in_error": Bool, 214 | #"have_output": Bool, 215 | #"have_positions": Bool, 216 | #"__filler": Bool, 217 | "_status": Unsigned_Int, 218 | 219 | "idx": Unsigned_Int, 220 | "len": Unsigned_Int, 221 | "out_len": Unsigned_Int, 222 | 223 | "allocated": Unsigned_Int, 224 | "info": ptr(hb_glyph_info_t), 225 | "out_info": ptr(hb_glyph_info_t), 226 | "pos": ptr(hb_glyph_position_t), 227 | 228 | "serial": Unsigned_Int, 229 | "allocated_var_bytes": array(uint8_t, 8), 230 | "allocated_var_owner": array(ptr(Char), 8), 231 | } 232 | 233 | define hb_buffer_t, "hb_buffer_create" 234 | define hb_buffer_t, "hb_buffer_reference", "buffer": hb_buffer_t 235 | define Void, "hb_buffer_destroy", "buffer": hb_buffer_t 236 | define Void, "hb_buffer_reset", "buffer": hb_buffer_t 237 | define hb_buffer_t, "hb_buffer_get_empty" 238 | define Void, "hb_buffer_set_content_type", "buffer": hb_buffer_t, "content_type": hb_buffer_content_type_t 239 | define Int, "hb_buffer_get_content_type", "buffer": hb_buffer_t 240 | define Unsigned_Int, "hb_buffer_get_length", "buffer": hb_buffer_t 241 | define hb_glyph_info_t, "hb_buffer_get_glyph_infos", "buffer": hb_buffer_t, "length": ptr(Unsigned_Int) 242 | define hb_glyph_position_t, "hb_buffer_get_glyph_positions", "buffer":hb_buffer_t, "length": ptr(Unsigned_Int) 243 | define Void, "hb_buffer_normalize_glyphs", "buffer": hb_buffer_t 244 | 245 | define Void, "hb_buffer_add", "buffer": hb_buffer_t, "codepoint": hb_codepoint_t, "mask": hb_mask_t, "cluster": Unsigned_Int 246 | define Void, "hb_buffer_add_utf8", "buffer": hb_buffer_t, "text": string, "text_length": Int, "item_offset": Unsigned_Int, "item_length": Int 247 | define Void, "hb_buffer_add_utf16", "buffer": hb_buffer_t, "text": ptr(uint16_t), "text_length": Int, "item_offset": Unsigned_Int, "item_length": Int 248 | define Void, "hb_buffer_add_utf32", "buffer": hb_buffer_t, "text": ptr(uint32_t), "text_length": Int, "item_offset": Unsigned_Int, "item_length": Int 249 | 250 | define Unsigned_Int, "hb_buffer_get_length", "buffer": hb_buffer_t 251 | 252 | define Void, "hb_buffer_guess_segment_properties", "buffer": hb_buffer_t 253 | 254 | define Void, "hb_buffer_set_direction", "buffer": hb_buffer_t, "direction": hb_direction_t 255 | define hb_direction_t, "hb_buffer_get_direction", "buffer": hb_buffer_t 256 | 257 | define Void, "hb_buffer_set_script", "buffer": hb_buffer_t, "script": hb_script_t 258 | define hb_script_t, "hb_buffer_get_script", "buffer": hb_buffer_t 259 | 260 | define Void, "hb_buffer_set_language", "buffer": hb_buffer_t, "language": hb_language_t 261 | define hb_language_t, "hb_buffer_get_language", "buffer": hb_buffer_t 262 | 263 | 264 | # 265 | # hb-blob.h 266 | # 267 | 268 | hb_memory_mode_t = Enumeration 269 | 270 | 271 | # 272 | # hb-blob.cc 273 | # 274 | 275 | hb_blob_t = typedef "hb_blob_t", struct { 276 | "header": hb_object_header_t, 277 | 278 | "immutable": Bool, 279 | 280 | "data": ptr(Char), 281 | "length": Unsigned_Int, 282 | "mode": hb_memory_mode_t, 283 | 284 | "user_data": ptr(Void), 285 | "destroy": hb_destroy_func_t, 286 | } 287 | 288 | # 289 | # hb-bloh.h again 290 | # 291 | 292 | define hb_blob_t, "hb_blob_create", "data": string, "length": Unsigned_Int, "mode": hb_memory_mode_t, "user_data": ptr(Void), "destroy": hb_destroy_func_t 293 | define hb_blob_t, "hb_blob_create_sub_blob", "parent": hb_blob_t, "offset": Unsigned_Int, "length": Unsigned_Int 294 | define hb_blob_t, "hb_blob_get_empty" 295 | define hb_blob_t, "hb_blob_reference", "blob": hb_blob_t 296 | define Void, "hb_blob_destroy", "blob": hb_blob_t 297 | 298 | 299 | # 300 | # hb-shaper-private.hh 301 | # 302 | 303 | HB_SHAPER_LIST = [ 304 | # TODO Not sure what to include here for now? 305 | "ot", 306 | "fallback" 307 | ] 308 | 309 | hb_shaper_data_t = typedef "hb_shaper_data_t", struct { 310 | "_shapers": array(ptr(Void), HB_SHAPER_LIST.length), 311 | } 312 | 313 | 314 | # 315 | # hb-shape-plan-private.hh 316 | # 317 | 318 | hb_shape_func_t = ptr(Void) # function 319 | 320 | 321 | # 322 | # hb-shape.h 323 | # 324 | 325 | hb_feature_t = typedef "hb_feature_t", struct { 326 | "tag": hb_tag_t, 327 | "value": uint32_t, 328 | "start": Unsigned_Int, 329 | "end": Unsigned_Int, 330 | } 331 | 332 | hb_font_t = typedef "hb_font_t", struct {} 333 | 334 | 335 | # len=-1 means str is NUL-terminated 336 | define hb_bool_t, "hb_feature_from_string", "str": string, "len": Int, "feature": hb_feature_t 337 | # something like 128 bytes is more than enough nul-terminates 338 | define Void, "hb_feature_to_string", "feature": hb_feature_t, "buf": string, "size": Int 339 | 340 | define ptr(ptr(Char)), "hb_shape_list_shapers" 341 | define Void, "hb_shape", "font": hb_font_t, "buffer": hb_buffer_t, "features": hb_feature_t, "num_features": Unsigned_Int 342 | define hb_bool_t, "hb_shape_full", "font": hb_font_t, "buffer": hb_buffer_t, "features": hb_feature_t, "num_features": Unsigned_Int, "shaper_list": ptr(ptr(Char)) 343 | 344 | 345 | # 346 | # hb-font-private.hh 347 | # 348 | 349 | HB_FONT_CALLBACKS = [ 350 | "glyph", 351 | "glyph_h_advance", 352 | "glyph_v_advance", 353 | "glyph_h_origin", 354 | "glyph_v_origin", 355 | "glyph_h_kerning", 356 | "glyph_v_kerning", 357 | "glyph_extents", 358 | "glyph_contour_point", 359 | "glyph_name", 360 | "glyph_from_name", 361 | ] 362 | 363 | hb_reference_table_func_t = ptr(Void) # function 364 | 365 | hb_font_funcs_t = typedef "hb_font_funcs_t", struct { 366 | "header": hb_object_header_t, 367 | 368 | "immutable": hb_bool_t, 369 | 370 | "get": array(ptr(Void), HB_FONT_CALLBACKS.length), 371 | "user_data": array(ptr(Void), HB_FONT_CALLBACKS.length), 372 | "destroy": array(ptr(Void), HB_FONT_CALLBACKS.length), 373 | } 374 | 375 | hb_face_t = typedef "hb_face_t", struct { 376 | "header": hb_object_header_t, 377 | 378 | "immutable": hb_bool_t, 379 | 380 | "reference_table_func": hb_reference_table_func_t, 381 | "user_data": ptr(Void), 382 | "destroy": hb_destroy_func_t, 383 | 384 | "index": Unsigned_Int, 385 | "upem": Unsigned_Int, 386 | 387 | "shaper_data": hb_shaper_data_t, 388 | 389 | "shape_plans": ptr(Void), 390 | } 391 | 392 | hb_font_t["redefine"] { 393 | "header": hb_object_header_t, 394 | 395 | "immutable": hb_bool_t, 396 | 397 | "parent": SelfPtr, 398 | "face": ptr(hb_face_t), 399 | 400 | "x_scale": Int, 401 | "y_scale": Int, 402 | 403 | "x_ppem": Unsigned_Int, 404 | "y_ppem": Unsigned_Int, 405 | 406 | "klass": ptr(hb_font_funcs_t), 407 | "user_data": ptr(Void), 408 | "destroy": hb_destroy_func_t, 409 | 410 | "shaper_data": hb_shaper_data_t, 411 | } 412 | 413 | 414 | # 415 | # hb-font.h 416 | # 417 | 418 | define hb_face_t, "hb_face_create", "blob": hb_blob_t, "index": Unsigned_Int 419 | define Void, "hb_face_destroy", "face": hb_face_t 420 | 421 | define hb_font_t, "hb_font_create", "face": hb_face_t 422 | define Void, "hb_font_destroy", "font": hb_font_t 423 | 424 | define Void, "hb_font_set_scale", "font": hb_font_t, "x_scale": Int, "y_scale": Int 425 | define hb_face_t, "hb_font_get_face", "font": hb_font_t 426 | 427 | define hb_font_funcs_t, "hb_font_funcs_create" 428 | for fontCallback in HB_FONT_CALLBACKS 429 | define Void, "hb_font_funcs_set_#{fontCallback}_func", "ffuncs": hb_font_funcs_t, "func": "i32", "user_data": ptr(Void), "destroy": hb_destroy_func_t 430 | define Void, "hb_font_funcs_destroy", "ffuncs": hb_font_funcs_t 431 | 432 | define Void, "hb_font_set_funcs", "font": hb_font_t, "klass": hb_font_funcs_t, "font_data": ptr(Void), "destroy": hb_destroy_func_t 433 | 434 | # 435 | # ucdn.h 436 | # 437 | 438 | define string, "ucdn_get_unicode_version" 439 | define Int, "ucdn_get_combining_class", "code": uint32_t 440 | define Int, "ucdn_get_east_asian_width", "code": uint32_t 441 | define Int, "ucdn_get_general_category", "code": uint32_t 442 | define Int, "ucdn_get_bidi_class", "code": uint32_t 443 | define Int, "ucdn_get_script", "code": uint32_t 444 | define Int, "ucdn_get_mirrored", "code": uint32_t 445 | define Int, "ucdn_mirror", "code": uint32_t 446 | define Int, "ucdn_decompose", "code": uint32_t, "a": ptr(uint32_t), "b": ptr(uint32_t) 447 | define Int, "ucdn_compose", "code": ptr(uint32_t), "a": uint32_t, "b": uint32_t 448 | -------------------------------------------------------------------------------- /src/main/emscripten/exports.txt: -------------------------------------------------------------------------------- 1 | # Core HarfBuzz stuff 2 | hb_language_from_string 3 | hb_language_to_string 4 | hb_unicode_funcs_get_default 5 | hb_unicode_funcs_reference 6 | hb_buffer_create 7 | hb_buffer_reference 8 | hb_buffer_destroy 9 | hb_buffer_reset 10 | hb_buffer_get_empty 11 | hb_buffer_set_content_type 12 | hb_buffer_get_content_type 13 | hb_buffer_get_length 14 | hb_buffer_get_glyph_infos 15 | hb_buffer_get_glyph_positions 16 | hb_buffer_normalize_glyphs 17 | hb_buffer_add 18 | hb_buffer_add_utf8 19 | hb_buffer_add_utf16 20 | hb_buffer_add_utf32 21 | hb_buffer_get_length 22 | hb_buffer_guess_segment_properties 23 | hb_buffer_set_direction 24 | hb_buffer_get_direction 25 | hb_buffer_set_script 26 | hb_buffer_get_script 27 | hb_buffer_set_language 28 | hb_buffer_get_language 29 | hb_blob_create 30 | hb_blob_create_sub_blob 31 | hb_blob_get_empty 32 | hb_blob_reference 33 | hb_blob_destroy 34 | hb_feature_from_string 35 | hb_feature_to_string 36 | hb_shape_list_shapers 37 | hb_shape 38 | hb_shape_full 39 | hb_face_create 40 | hb_face_destroy 41 | hb_font_create 42 | hb_font_destroy 43 | hb_font_set_scale 44 | hb_font_get_face 45 | hb_font_funcs_create 46 | hb_font_funcs_destroy 47 | hb_font_set_funcs 48 | hb_font_funcs_set_glyph_func 49 | hb_font_funcs_set_glyph_h_advance_func 50 | hb_font_funcs_set_glyph_v_advance_func 51 | hb_font_funcs_set_glyph_h_origin_func 52 | hb_font_funcs_set_glyph_v_origin_func 53 | hb_font_funcs_set_glyph_h_kerning_func 54 | hb_font_funcs_set_glyph_v_kerning_func 55 | hb_font_funcs_set_glyph_extents_func 56 | hb_font_funcs_set_glyph_contour_point_func 57 | hb_font_funcs_set_glyph_name_func 58 | hb_font_funcs_set_glyph_from_name_func 59 | 60 | # Unicode stuff 61 | ucdn_get_unicode_version 62 | ucdn_get_combining_class 63 | ucdn_get_east_asian_width 64 | ucdn_get_general_category 65 | ucdn_get_bidi_class 66 | ucdn_get_script 67 | ucdn_get_mirrored 68 | ucdn_mirror 69 | ucdn_decompose 70 | ucdn_compose 71 | -------------------------------------------------------------------------------- /src/test/coffee/connectorTest.coffee: -------------------------------------------------------------------------------- 1 | chai = require "chai" 2 | should = chai.should() 3 | expect = chai.expect 4 | chai.Assertion.includeStack = true 5 | 6 | module = require "../../../build/harfbuzz-test" 7 | [struct, array, ptr, string, define, callback, typedef, free] = [module.struct, module.array, module.ptr, module.string, module.define, module.callback, module.typedef, module.free] 8 | [SelfPtr, Void] = [module.SelfPtr, module.Void] 9 | [allocate, _free, intArrayFromString] = [module.allocate, module._free, module.intArrayFromString] 10 | 11 | using = (tests..., func) -> 12 | func() 13 | for test in tests 14 | free test 15 | return 16 | 17 | describe "Empty struct", -> 18 | test_t = struct { 19 | # Empty struct 20 | } 21 | 22 | it "should be a function", -> 23 | should.exist test_t 24 | test_t.should.be.a "function" 25 | 26 | it "should have a size of 0", -> 27 | test_t.resolve() 28 | test_t.size.should.equal 0 29 | 30 | it "should create an empty struct instance", -> 31 | using test = new test_t(), -> 32 | test.toString().should.equal "{ }" 33 | expect(test.field).not.to.exist 34 | 35 | it "should have a non-null pointer", -> 36 | using test = new test_t(), -> 37 | test.$ptr.should.be.above 0 38 | 39 | it "should throw an error if it is freed for a second time", -> 40 | test = new test_t() 41 | free test 42 | expect(-> free test).to.throw # something 43 | 44 | 45 | describe "Simple struct of a single i32 field", -> 46 | test_t = struct { 47 | field: "i32", 48 | } 49 | test_t.resolve() 50 | 51 | it "should have a size of 4", -> 52 | test_t.size.should.equal 4 53 | 54 | it "should create an instance initialized with zeroes", -> 55 | using test = new test_t(), -> 56 | test.toString().should.equal "{ field: 0 }" 57 | expect(test.get("field")).to.exist 58 | 59 | it "should have a r/w number field called 'field'", -> 60 | using test = new test_t(), -> 61 | expect(test.get("field")).to.be.a "number" 62 | test.get("field").should.equal 0 63 | module.getValue(test.$ptr, "i32").should.equal 0 64 | test.set "field", 12 65 | test.get("field").should.equal 12 66 | module.getValue(test.$ptr, "i32").should.equal 12 67 | test.set "field", -12 68 | test.get("field").should.equal -12 69 | module.getValue(test.$ptr, "i32").should.equal -12 70 | 71 | it "should truncate non-integers", -> 72 | using test = new test_t(), -> 73 | test.set "field", -12.8 74 | test.get("field").should.equal -12 75 | module.getValue(test.$ptr, "i32").should.equal -12 76 | 77 | it "should have a toString()", -> 78 | using test = new test_t(), -> 79 | test.set "field", 123 80 | test.toString().should.equal "{ field: 123 }" 81 | test.set "field", -443 82 | test.toString().should.equal "{ field: -443 }" 83 | 84 | 85 | describe "Complex struct of i32, i8, i16, i1", -> 86 | test_t = struct { 87 | f1: "i8", 88 | f2: "i16", 89 | f3: "i32", 90 | f4: "i64", 91 | f5: "i1", 92 | } 93 | test_t.resolve() 94 | 95 | it "should have a size of 24 (i.e. 4 x 32 bit + 1 x 64 bit)", -> 96 | test_t.size.should.equal 4 * 4 + 1 * 8 97 | 98 | it "should have the right fields initialized to 0", -> 99 | using test = new test_t(), -> 100 | test.get("f1").should.equal 0 101 | test.get("f2").should.equal 0 102 | test.get("f3").should.equal 0 103 | test.get("f4").should.equal 0 104 | test.get("f5").should.equal 0 105 | 106 | it "should be assignable", -> 107 | using test = new test_t(), -> 108 | test.set "f1", 1 109 | test.set "f2", 2 110 | test.set "f3", 3 111 | test.set "f4", 4 112 | test.set "f5", 0 113 | 114 | test.get("f1").should.equal 1 115 | test.get("f2").should.equal 2 116 | test.get("f3").should.equal 3 117 | test.get("f4").should.equal 4 118 | test.get("f5").should.equal 0 119 | 120 | test.set "f2", 123 121 | test.get("f1").should.equal 1 122 | test.get("f2").should.equal 123 123 | test.get("f3").should.equal 3 124 | test.get("f4").should.equal 4 125 | test.get("f5").should.equal 0 126 | 127 | it "should truncate non-integers", -> 128 | using test = new test_t(), -> 129 | test.set "f1", 1.2 130 | test.set "f2", 2.9 131 | test.set "f3", 3.1 132 | test.set "f4", 4.5 133 | test.set "f5", 17.9 134 | 135 | test.get("f1").should.equal 1 136 | test.get("f2").should.equal 2 137 | test.get("f3").should.equal 3 138 | test.get("f4").should.equal 4 139 | # "i1" is actually the same as "i8" 140 | test.get("f5").should.equal 17 141 | 142 | it "should handle overflows", -> 143 | using test = new test_t(), -> 144 | test.set "f1", Math.pow(2, 8) + 1 145 | test.set "f2", Math.pow(2, 16) + 2 146 | test.set "f3", Math.pow(2, 32) + 3 147 | test.set "f4", Math.pow(2, 32) + 4 148 | test.set "f5", Math.pow(2, 8) + 7 149 | 150 | test.get("f1").should.equal 1 151 | test.get("f2").should.equal 2 152 | test.get("f3").should.equal 3 153 | # "i64" is basically "i32" for us 154 | # TODO is this okay? 155 | test.get("f4").should.equal 4 156 | test.get("f5").should.equal 7 157 | 158 | 159 | describe "Complex struct of float, double", -> 160 | test_t = struct { 161 | f1: "float", 162 | f2: "double", 163 | } 164 | test_t.resolve() 165 | 166 | it "should have a size of 12", -> 167 | test_t.size.should.equal 4 + 8 168 | 169 | it "should handle floating point values", -> 170 | using test = new test_t(), -> 171 | test.set "f1", 1.2 172 | test.set "f2", 2.9 173 | # Float is broken with i386-linux target, beware! 174 | # Only use doubles 175 | # test.get("f1").should.be.closeTo 1.2, 0.001 176 | test.get("f2").should.equal 2.9 177 | 178 | 179 | describe "Nested struct", -> 180 | child_t = struct { 181 | c1: "i32", 182 | c2: "i32", 183 | } 184 | parent_t = struct { 185 | p0: "i32", 186 | p1: child_t, 187 | p2: "i32", 188 | p3: child_t, 189 | p4: "i32", 190 | } 191 | parent_t.resolve() 192 | 193 | it "should have the correct sizes", -> 194 | child_t.size.should.equal 4 + 4 195 | parent_t.size.should.equal 4 + 8 + 4 + 8 + 4 196 | 197 | it "should be created and initialized to all zero", -> 198 | using test = new parent_t(), -> 199 | test.get("p0").should.be.a "number" 200 | test.get("p1").should.be.an "object" 201 | test.get("p1").get("c1").should.be.a "number" 202 | test.get("p1").get("c2").should.be.a "number" 203 | test.get("p2").should.be.a "number" 204 | test.get("p3").should.be.an "object" 205 | test.get("p3").get("c1").should.be.a "number" 206 | test.get("p3").get("c2").should.be.a "number" 207 | test.get("p4").should.be.a "number" 208 | 209 | test.get("p0").should.equal 0 210 | test.get("p1").get("c1").should.equal 0 211 | test.get("p1").get("c2").should.equal 0 212 | test.get("p2").should.equal 0 213 | test.get("p3").get("c1").should.equal 0 214 | test.get("p3").get("c2").should.equal 0 215 | test.get("p4").should.equal 0 216 | 217 | it "should have children that can be freed any number of times", -> 218 | using test = new parent_t(), -> 219 | # Apparently Emscripten has no problem with 220 | # freeing non-malloc'd data multiple times 221 | free test.get("p1") 222 | free test.get("p1") 223 | 224 | it "should have working nested properties", -> 225 | using test = new parent_t(), -> 226 | test.set "p0", 11 227 | test.get("p1").set "c1", 7 228 | test.get("p1").get("c1").should.equal 7 229 | test.get("p1").set "c2", -12 230 | test.get("p1").get("c2").should.equal -12 231 | module.getValue(test.$ptr + 0, "i32").should.equal 11 232 | module.getValue(test.$ptr + 4, "i32").should.equal 7 233 | module.getValue(test.$ptr + 8, "i32").should.equal -12 234 | module.getValue(test.get("p1").$ptr + 0, "i32").should.equal 7 235 | module.getValue(test.get("p1").$ptr + 4, "i32").should.equal -12 236 | 237 | it "should have a toString()", -> 238 | using test = new parent_t(), -> 239 | test.set "p0", 1 240 | test.get("p1").set "c1", 2 241 | test.get("p1").set "c2", 3 242 | test.set "p2", 4 243 | test.get("p3").set "c1", 5 244 | test.get("p3").set "c2", 6 245 | test.set "p4", 7 246 | test.toString().should.equal "{ p0: 1, p1: { c1: 2, c2: 3 }, p2: 4, p3: { c1: 5, c2: 6 }, p4: 7 }" 247 | 248 | it "should have nested properties assignable", -> 249 | using parent = new parent_t(), child = new child_t(), -> 250 | parent.set "p0", 1 251 | parent.get("p1").set "c1", 2 252 | parent.get("p1").set "c2", 3 253 | parent.set "p2", 4 254 | parent.get("p3").set "c1", 5 255 | parent.get("p3").set "c2", 6 256 | parent.set "p4", 7 257 | 258 | child.set "c1", 8 259 | child.set "c2", 10 260 | 261 | parent.set "p1", child 262 | parent.get("p1").should.not.equal child 263 | parent.get("p1").get("c1").should.equal 8 264 | parent.get("p1").get("c2").should.equal 10 265 | 266 | it "should refuse to set nested property to incompatible struct", -> 267 | other_t = struct { 268 | c1: "i32", 269 | c2: "i32", 270 | } 271 | using parent = new parent_t(), other = new other_t(), -> 272 | expect(-> parent.set("p1", other)).to.throw /Cannot load/ 273 | 274 | 275 | describe "Redefined struct", -> 276 | struct1_t = typedef "struct1", struct {} 277 | struct2_2 = typedef "struct2", struct { 278 | field1: struct1_t, 279 | field2: struct1_t 280 | } 281 | struct1_t.redefine { 282 | value: "i32" 283 | } 284 | 285 | it "should work", -> 286 | using s2 = new struct2_2(), \ 287 | s1 = new struct1_t(), -> 288 | 289 | s1.set "value", 12.3 290 | s2.get("field1").set "value", 123.7 291 | 292 | # Rounding should prove that we are using our own properties 293 | s2.get("field1").get("value").should.equal 123 294 | s2.set "field2", s1 295 | s2.get("field2").get("value").should.equal 12 296 | 297 | 298 | describe "Recursive struct", -> 299 | test_t = typedef "test_t", struct { 300 | parent: SelfPtr, 301 | value: "i32" 302 | } 303 | test_t.resolve() 304 | 305 | it "should have a toString()", -> 306 | test_t.toString().should.equal "{ parent: *test_t, value: i32 }" 307 | 308 | describe "Empty array", -> 309 | test_t = array "i32", 0 310 | test_t.resolve() 311 | 312 | it "should be a function", -> 313 | should.exist test_t 314 | test_t.should.be.a "function" 315 | 316 | it "should have a size of 0", -> 317 | test_t.size.should.equal 0 318 | test_t.count.should.equal 0 319 | 320 | it "should create an empty array instance", -> 321 | using test = new test_t(), -> 322 | test.toString().should.equal "[ ]" 323 | 324 | it "should have a non-null pointer", -> 325 | using test = new test_t(), -> 326 | test.$ptr.should.be.above 0 327 | 328 | it "should throw an error if it is freed for a second time", -> 329 | test = new test_t() 330 | free test 331 | expect(-> free test).to.throw # something 332 | 333 | 334 | describe "Simple array of a single i32 field", -> 335 | test_t = array "i32", 1 336 | test_t.resolve() 337 | 338 | it "should have a size of 4", -> 339 | test_t.size.should.equal 4 340 | 341 | it "should create an instance initialized with zeroes", -> 342 | using test = new test_t(), -> 343 | test.toString().should.equal "[ 0 ]" 344 | 345 | it "should have a r/w element", -> 346 | using test = new test_t(), -> 347 | expect(test.get(0)).to.be.a "number" 348 | test.get(0).should.equal 0 349 | module.getValue(test.$ptr, "i32").should.equal 0 350 | test.set 0, 12 351 | test.get(0).should.equal 12 352 | module.getValue(test.$ptr, "i32").should.equal 12 353 | test.set 0, -12 354 | test.get(0).should.equal -12 355 | module.getValue(test.$ptr, "i32").should.equal -12 356 | 357 | it "should truncate non-integers", -> 358 | using test = new test_t(), -> 359 | test.set 0, -12.8 360 | test.get(0).should.equal -12 361 | module.getValue(test.$ptr, "i32").should.equal -12 362 | 363 | it "should check index overflows", -> 364 | using test = new test_t(), -> 365 | expect(-> test.set 1, 1).to.throw /Index out of bounds: 0 <= 1 < 1/ 366 | expect(-> test.set -1, 1).to.throw /Index out of bounds: 0 <= -1 < 1/ 367 | module.getValue(test.$ptr, "i32").should.equal 0 368 | 369 | it "should have a toString()", -> 370 | using test = new test_t(), -> 371 | test.set 0, 123 372 | test.toString().should.equal "[ 123 ]" 373 | test.set 0, -443 374 | test.toString().should.equal "[ -443 ]" 375 | 376 | 377 | describe "Size 5 array of i8", -> 378 | test_t = array "i8", 5 379 | test_t.resolve() 380 | 381 | it "should have a size of 20 (i.e. 5 * 8 bit)", -> 382 | test_t.size.should.equal 5 383 | 384 | it "should have been initialized to 0", -> 385 | using test = new test_t(), -> 386 | for i in [0...5] 387 | test.get(i).should.equal 0 388 | 389 | it "should be assignable", -> 390 | using test = new test_t(), -> 391 | for i in [0...5] 392 | test.set i, i + 1 393 | test.get(0).should.equal 1 394 | test.get(1).should.equal 2 395 | test.get(2).should.equal 3 396 | test.get(3).should.equal 4 397 | test.get(4).should.equal 5 398 | 399 | test.set 1, 123 400 | test.get(0).should.equal 1 401 | test.get(1).should.equal 123 402 | test.get(2).should.equal 3 403 | test.get(3).should.equal 4 404 | test.get(4).should.equal 5 405 | 406 | it "should truncate non-integers", -> 407 | using test = new test_t(), -> 408 | test.set 0, 1.2, 409 | test.set 1, 2.9 410 | test.set 2, 3.1 411 | test.set 3, 4.5 412 | test.set 4, 17.9 413 | test.get(0).should.equal 1 414 | test.get(1).should.equal 2 415 | test.get(2).should.equal 3 416 | test.get(3).should.equal 4 417 | test.get(4).should.equal 17 418 | 419 | it "should handle overflows", -> 420 | using test = new test_t(), -> 421 | test.set 0, Math.pow(2, 8) + 1 422 | test.set 1, Math.pow(2, 16) + 2 423 | test.set 2, Math.pow(2, 32) + 3 424 | test.set 3, Math.pow(2, 32) + 4 425 | test.set 4, Math.pow(2, 8) + 7 426 | 427 | test.get(0).should.equal 1 428 | test.get(1).should.equal 2 429 | test.get(2).should.equal 3 430 | # "i64" is basically "i32" for us 431 | # TODO is this okay? 432 | test.get(3).should.equal 4 433 | test.get(4).should.equal 7 434 | 435 | 436 | describe "Array of float", -> 437 | test_t = array "float", 3 438 | test_t.resolve() 439 | 440 | it "should have a size of 12", -> 441 | test_t.size.should.equal 3 * 4 442 | 443 | it "should handle floating point values", -> 444 | using test = new test_t(), -> 445 | test.set 0, 1.2 446 | test.set 1, 2.9 447 | test.set 2, 14.1 448 | 449 | test.get(0).should.be.closeTo 1.2, 0.001 450 | test.get(1).should.be.closeTo 2.9, 0.001 451 | test.get(2).should.be.closeTo 14.1, 0.001 452 | 453 | 454 | describe "Array of double", -> 455 | test_t = array "double", 3 456 | test_t.resolve() 457 | 458 | it "should have a size of 24", -> 459 | test_t.size.should.equal 3 * 8 460 | 461 | it "should handle floating point values", -> 462 | using test = new test_t(), -> 463 | test.set 0, 1.2 464 | test.set 1, 2.9 465 | test.set 2, 14.1 466 | 467 | test.get(0).should.equal 1.2 468 | test.get(1).should.equal 2.9 469 | test.get(2).should.equal 14.1 470 | 471 | 472 | describe "Array of struct", -> 473 | child_t = struct { 474 | c1: "i32", 475 | c2: "i32", 476 | } 477 | parent_t = array child_t, 2 478 | parent_t.resolve() 479 | 480 | it "should have the correct sizes", -> 481 | child_t.size.should.equal 2 * 4 482 | parent_t.size.should.equal 2 * 2 * 4 483 | 484 | it "should be created and initialized to all zero", -> 485 | using test = new parent_t(), -> 486 | test.get(0).should.be.an "object" 487 | test.get(1).should.be.an "object" 488 | 489 | test.get(0).get("c1").should.equal 0 490 | test.get(0).get("c2").should.equal 0 491 | test.get(1).get("c1").should.equal 0 492 | test.get(1).get("c2").should.equal 0 493 | 494 | it "should throw error if element is freed", -> 495 | using test = new parent_t(), -> 496 | expect(-> free test.get 0).to.throw # something 497 | 498 | it "should have working nested structs", -> 499 | using test = new parent_t(), -> 500 | test.get(0).set "c1", 7 501 | test.get(0).get("c1").should.equal 7 502 | test.get(1).set "c2", -12 503 | test.get(1).get("c2").should.equal -12 504 | module.getValue(test.$ptr + 0, "i32").should.equal 7 505 | module.getValue(test.$ptr + 4, "i32").should.equal 0 506 | module.getValue(test.$ptr + 8, "i32").should.equal 0 507 | module.getValue(test.$ptr + 12, "i32").should.equal -12 508 | module.getValue(test.get(0).$ptr + 0, "i32").should.equal 7 509 | module.getValue(test.get(1).$ptr + 4, "i32").should.equal -12 510 | 511 | it "should be created and initialized with values", -> 512 | using test = new parent_t(), -> 513 | test.get(0).set "c1", 2 514 | test.get(0).set "c2", 3 515 | test.get(1).set "c1", 5 516 | test.get(1).set "c2", 6 517 | 518 | test.get(0).get("c1").should.equal 2 519 | test.get(0).get("c2").should.equal 3 520 | test.get(1).get("c1").should.equal 5 521 | test.get(1).get("c2").should.equal 6 522 | 523 | it "should have a toString()", -> 524 | using test = new parent_t(), -> 525 | test.get(0).set "c1", 2 526 | test.get(0).set "c2", 3 527 | test.get(1).set "c1", 5 528 | test.get(1).set "c2", 6 529 | 530 | test.toString().should.equal "[ { c1: 2, c2: 3 }, { c1: 5, c2: 6 } ]" 531 | 532 | it "should have nested properties assignable", -> 533 | using parent = new parent_t(), \ 534 | child = new child_t(), -> 535 | parent.get(0).set "c1", 2 536 | parent.get(0).set "c2", 3 537 | parent.get(1).set "c1", 5 538 | parent.get(1).set "c2", 6 539 | child.set "c1", 8 540 | child.set "c2", 10 541 | 542 | parent.set 0, child 543 | parent.get(0).should.not.equal child 544 | parent.get(0).get("c1").should.equal 8 545 | parent.get(0).get("c2").should.equal 10 546 | 547 | it "should refuse to set nested property to incompatible struct", -> 548 | other_t = struct { 549 | c1: "i32", 550 | c2: "i32", 551 | } 552 | using parent = new parent_t(), \ 553 | other = new other_t(), -> 554 | parent.get(0).set "c1", 2 555 | parent.get(0).set "c2", 3 556 | parent.get(1).set "c1", 5 557 | parent.get(1).set "c2", 6 558 | other.set "c1", 8 559 | other.set "c2", 10 560 | 561 | expect(-> parent.set 0, other).to.throw /Cannot load/ 562 | 563 | 564 | target_t = struct { 565 | field: "i32", 566 | } 567 | target_ptr = ptr(target_t) 568 | 569 | describe "Null pointer", -> 570 | 571 | it "should point to NULL", -> 572 | using test = new target_ptr(), -> 573 | expect(test.get()).to.be.null 574 | 575 | it "should have a toString() that returns NULL", -> 576 | using test = new target_ptr(), -> 577 | test.toString().should.equal "NULL" 578 | 579 | describe "Simple pointer", -> 580 | 581 | it "should describe itself", -> 582 | target_ptr.toString().should.equal "*{ field: i32 }" 583 | 584 | it "should point to the right object", -> 585 | heap = allocate 1, "i32", module.ALLOC_NORMAL 586 | using test = new target_ptr(heap), \ 587 | target = new target_t(), -> 588 | test.setAddress target.$ptr 589 | test.get().$ptr.should.equal target.$ptr 590 | 591 | test.setAddress 0 592 | expect(test.get()).to.be.null 593 | #module._free heap 594 | 595 | it "should have a toString()", -> 596 | heap = allocate 1, "i32", module.ALLOC_NORMAL 597 | using test = new target_ptr(heap), \ 598 | target = new target_t(), -> 599 | target.set "field", 12345 600 | test.setAddress target.$ptr 601 | test.toString().should.equal "@#{target.$ptr}->{ field: 12345 }" 602 | #module._free heap 603 | 604 | describe "Local pointer", -> 605 | 606 | it "should describe itself", -> 607 | target_ptr.toString().should.equal "*{ field: i32 }" 608 | 609 | it "should point to the right object", -> 610 | test = new target_ptr() 611 | using target = new target_t(), -> 612 | test.setAddress target.$ptr 613 | test.get().$ptr.should.equal target.$ptr 614 | 615 | test.setAddress 0 616 | expect(test.get()).to.be.null 617 | 618 | it "should have a toString()", -> 619 | using test = new target_ptr(), \ 620 | target = new target_t(), -> 621 | target.set "field", 12345 622 | test.setAddress target.$ptr 623 | test.toString().should.equal "@#{target.$ptr}->{ field: 12345 }" 624 | 625 | ### 626 | testString = define string, "testString", prefix: string, len: "i32" 627 | 628 | describe "Strings", -> 629 | it "should work as parameters and return values", -> 630 | using sWorld = new string("World"), -> 631 | sGreet = testString sWorld 632 | sGreet.toString().should.equal "World, hello" 633 | 634 | doSomething = define "i32", "doSomething", a: "i32", b: "i32", op: "i32" 635 | printSomething = define "i32", "printSomething" 636 | addFunc = callback "i32", "addFunc", {a: "i32", b: "i32" }, (a, b) -> a + b 637 | 638 | describe "External function", -> 639 | it "should be called", -> 640 | res = doSomething 10, 21, addFunc 641 | # console.log "Res: #{res}" 642 | res.should.equal 31 643 | 644 | describe "String", -> 645 | it "when just allocated should have a toString() '(null)'", -> 646 | using test = new string(), -> 647 | test.toString().should.equal "(null)" 648 | 649 | it "should return the right string when address is pointed to a string", -> 650 | using test = new string(), -> 651 | test.set "address", allocate intArrayFromString("Lajos"), "i8", module.ALLOC_STACK 652 | test.toString().should.equal "Lajos" 653 | 654 | 655 | describe "Local string", -> 656 | it "should have a toString() of (null) by default", -> 657 | using test = new localString(), -> 658 | test.toString().should.equal "(null)" 659 | 660 | it "should return the right string when allocated to a string", -> 661 | test = new localString(allocate intArrayFromString("Tibor"), "i8", module.ALLOC_STACK) 662 | test.toString().should.equal "Tibor" 663 | ### 664 | -------------------------------------------------------------------------------- /src/test/coffee/harfbuzzTest.coffee: -------------------------------------------------------------------------------- 1 | chai = require "chai" 2 | should = chai.should() 3 | expect = chai.expect 4 | chai.Assertion.includeStack = true 5 | 6 | harfbuzz = require "../../../build/harfbuzz-test" 7 | fs = require "fs" 8 | 9 | using = (buffers..., func) -> 10 | func() 11 | for buffer in buffers 12 | harfbuzz.hb_buffer_destroy buffer 13 | return 14 | 15 | describe "Buffer", -> 16 | buffer = null 17 | 18 | it "should be created via hb_buffer_create()", -> 19 | buffer = harfbuzz.hb_buffer_create() 20 | # console.log "Buffer: #{buffer.$ptr}\n\n#{buffer}" 21 | buffer.get("header").get("ref_count").get("ref_count").should.equal 1 22 | 23 | it "should add a reference via hb_buffer_reference()", -> 24 | # console.log "==================== #{buffer}" 25 | buffer = harfbuzz.hb_buffer_reference buffer 26 | # console.log "Buffer: #{buffer.$ptr}\n\n#{buffer}" 27 | buffer.get("header").get("ref_count").get("ref_count").should.equal 2 28 | 29 | it "should remove a reference via hb_buffer_destroy()", -> 30 | harfbuzz.hb_buffer_destroy buffer 31 | buffer.get("header").get("ref_count").get("ref_count").should.equal 1 32 | 33 | it "should completely destroy itself via another call to hb_buffer_destroy()", -> 34 | harfbuzz.hb_buffer_destroy buffer 35 | 36 | it "should allow further calls to hb_buffer_destroy() once destroyed", -> 37 | harfbuzz.hb_buffer_destroy buffer 38 | harfbuzz.hb_buffer_destroy buffer 39 | 40 | 41 | describe "Adding to buffer", -> 42 | it "should work", -> 43 | using buffer = harfbuzz.hb_buffer_create(), -> 44 | harfbuzz.hb_buffer_get_length(buffer).should.equal 0 45 | harfbuzz.hb_buffer_add buffer, 65, 1, 0 46 | harfbuzz.hb_buffer_get_length(buffer).should.equal 1 47 | 48 | 49 | describe "Empty buffer", -> 50 | buffer = null 51 | 52 | it "should be available", -> 53 | buffer = harfbuzz.hb_buffer_get_empty() 54 | buffer.get("header").get("ref_count").get("ref_count").should.equal -1 55 | # console.log "Buffer #{buffer.$ptr}:\n#{buffer}" 56 | 57 | 58 | glyph_h_advance_func = harfbuzz.callback harfbuzz.hb_position_t, "glyph_h_advance_func", { 59 | font: harfbuzz.ptr(harfbuzz.hb_font_t), 60 | font_data: harfbuzz.ptr(harfbuzz.Void), 61 | glyph: harfbuzz.hb_codepoint_t, 62 | user_data: harfbuzz.ptr(harfbuzz.Void) 63 | }, (font, font_data, glyph, user_data) -> 64 | hAdvance = switch glyph 65 | when 0x50 then 10 66 | when 0x69 then 6 67 | when 0x6E then 5 68 | else 9 69 | 70 | console.log "Advance for #{glyph} is #{hAdvance}" 71 | 72 | return hAdvance 73 | 74 | glyph_h_kerning_func = harfbuzz.callback harfbuzz.hb_position_t, "glyph_h_kerning_func", { 75 | font: harfbuzz.ptr(harfbuzz.hb_font_t), 76 | font_data: harfbuzz.ptr(harfbuzz.Void), 77 | left: harfbuzz.hb_codepoint_t, 78 | right: harfbuzz.hb_codepoint_t, 79 | user_data: harfbuzz.ptr(harfbuzz.Void) 80 | }, (font, font_data, left, right, user_data) -> 81 | return 0 82 | 83 | glyph_func = harfbuzz.callback harfbuzz.Bool, "glyph_func", { 84 | font: harfbuzz.ptr(harfbuzz.hb_font_t), 85 | font_data: harfbuzz.ptr(harfbuzz.Void), 86 | unicode: harfbuzz.hb_codepoint_t, 87 | variant_selector: harfbuzz.hb_codepoint_t, 88 | glyph: harfbuzz.ptr(harfbuzz.hb_codepoint_t), 89 | user_data: harfbuzz.ptr(harfbuzz.Void) 90 | }, (font, font_data, unicode, variant_selector, glyph, user_data) -> 91 | harfbuzz.setValue glyph.address, unicode, "i32" 92 | console.log "Called with unicode: #{unicode}, glyph is #{glyph.get()}" 93 | 94 | return true 95 | 96 | describe "Feature", -> 97 | it "should be converted to string", -> 98 | feature = new harfbuzz.hb_feature_t() 99 | feature.set "tag", harfbuzz.HB_TAG "kern" 100 | feature.set "value", 0 101 | feature.set "start", 0 102 | feature.set "end", 0 103 | 104 | bufPtr = new (harfbuzz.ptr(harfbuzz.Char))() 105 | bufPtr.address = harfbuzz.allocate 128, "i8", harfbuzz.ALLOC_STACK 106 | harfbuzz.hb_feature_to_string feature, bufPtr, 128 107 | console.log "Feature: " + bufPtr 108 | 109 | it "should be creatable from string", -> 110 | feature = new harfbuzz.hb_feature_t() 111 | bufPtr = new (harfbuzz.ptr(harfbuzz.Char))() 112 | bufPtr.address = harfbuzz.allocate harfbuzz.intArrayFromString("kern"), "i8", harfbuzz.ALLOC_STACK 113 | harfbuzz.hb_feature_from_string bufPtr, 128, feature 114 | console.log "Feature: " + feature 115 | 116 | it "should be creatable from string (2)", -> 117 | feature = new harfbuzz.hb_feature_t() 118 | bufPtr = new harfbuzz.string("kern") 119 | console.log "Buffer's heap: #{bufPtr.$ptr}, #{bufPtr.$type}, #{typeof bufPtr.$type}" 120 | harfbuzz.hb_feature_from_string bufPtr, -1, feature 121 | console.log "Feature: " + feature 122 | 123 | describe "Loading a blob", -> 124 | it "should work", -> 125 | dataBuffer = fs.readFileSync("src/test/resources/OpenBaskerville-0.0.75.otf", "binary") 126 | data = harfbuzz.allocate dataBuffer, "i8", harfbuzz.ALLOC_NORMAL 127 | for i in [0...dataBuffer.length] 128 | harfbuzz.setValue data + i, dataBuffer[i], "i8" 129 | 130 | blob = harfbuzz.hb_blob_create data, dataBuffer.length, 1, null, null 131 | 132 | face = harfbuzz.hb_face_create blob, 0 133 | # console.log "Face: #{face}" 134 | harfbuzz.hb_blob_destroy blob 135 | 136 | font = harfbuzz.hb_font_create face 137 | # console.log "Face: #{font.face}" 138 | harfbuzz.hb_face_destroy face 139 | harfbuzz.hb_font_set_scale font, 10, 10 140 | # console.log "Font: #{font}" 141 | 142 | ffuncs = harfbuzz.hb_font_funcs_create(); 143 | harfbuzz.hb_font_funcs_set_glyph_h_advance_func ffuncs, glyph_h_advance_func, null, null 144 | harfbuzz.hb_font_funcs_set_glyph_func ffuncs, glyph_func, null, null 145 | harfbuzz.hb_font_funcs_set_glyph_h_kerning_func ffuncs, glyph_h_kerning_func, null, null 146 | harfbuzz.hb_font_set_funcs font, ffuncs, null, null 147 | # console.log "FFuncs: #{ffuncs}" 148 | harfbuzz.hb_font_funcs_destroy ffuncs 149 | 150 | buffer = harfbuzz.hb_buffer_create() 151 | #harfbuzz.hb_buffer_set_content_type buffer, 1 # HB_BUFFER_CONTENT_TYPE_UNICODE 152 | #harfbuzz.hb_buffer_set_direction buffer, 4 # HB_DIRECTION_LTR 153 | 154 | str = new harfbuzz.string "Bacon", harfbuzz.ALLOC_STACK 155 | harfbuzz.hb_buffer_add_utf8 buffer, str, -1, 0, -1 156 | harfbuzz.hb_buffer_guess_segment_properties buffer 157 | 158 | harfbuzz.hb_shape font, buffer, null, 0, null 159 | len = harfbuzz.hb_buffer_get_length buffer 160 | console.log "Buffer length: #{len}" 161 | 162 | lenPtr = new (harfbuzz.ptr(harfbuzz.Void))() 163 | 164 | glyph = harfbuzz.hb_buffer_get_glyph_infos buffer, lenPtr 165 | pos = harfbuzz.hb_buffer_get_glyph_positions buffer, null 166 | console.log "Ptr'd len: #{lenPtr.get()}" 167 | for i in [0...len] 168 | console.log "Glyph ##{i}: #{glyph} at #{pos}" 169 | glyph = glyph.$next() 170 | pos = pos.$next() 171 | 172 | harfbuzz.hb_buffer_destroy (buffer) 173 | harfbuzz.hb_font_destroy (font) 174 | 175 | 176 | ### 177 | describe "Shapers", -> 178 | it "should be available: ot, fallback", -> 179 | shapers = harfbuzz.hb_shape_list_shapers() 180 | console.log "Shapers: #{shapers}" 181 | console.log "Shaper: #{shapers.toString()}" 182 | shapers.toString().should.equal "ot" 183 | shapers.$next().toString().should.equal "fallback" 184 | shapers.$next().$next().address.should.equal 0 185 | ### 186 | -------------------------------------------------------------------------------- /src/test/js/harfbuzz-test-post.js: -------------------------------------------------------------------------------- 1 | module.exports = Module; 2 | -------------------------------------------------------------------------------- /src/test/js/harfbuzz-test-pre.js: -------------------------------------------------------------------------------- 1 | var Module = {}; 2 | -------------------------------------------------------------------------------- /src/test/resources/OpenBaskerville-0.0.75.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prezi/harfbuzz-js/735fc0b8e784f31ab82f16e481ada7e0c1669e5d/src/test/resources/OpenBaskerville-0.0.75.otf --------------------------------------------------------------------------------