mirror of
https://github.com/curioustorvald/Terrarum-sans-bitmap.git
synced 2026-03-07 11:51:50 +09:00
bitsnpicas probably not decent
This commit is contained in:
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -4,6 +4,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/BuildJAR_TerrarumSansBitmap.iml" filepath="$PROJECT_DIR$/BuildJAR_TerrarumSansBitmap.iml" />
|
<module fileurl="file://$PROJECT_DIR$/BuildJAR_TerrarumSansBitmap.iml" filepath="$PROJECT_DIR$/BuildJAR_TerrarumSansBitmap.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/FontTestGDX/FontTestGDX.iml" filepath="$PROJECT_DIR$/FontTestGDX/FontTestGDX.iml" />
|
<module fileurl="file://$PROJECT_DIR$/FontTestGDX/FontTestGDX.iml" filepath="$PROJECT_DIR$/FontTestGDX/FontTestGDX.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/OTFbuild/OTFbuild.iml" filepath="$PROJECT_DIR$/OTFbuild/OTFbuild.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
106
.idea/workspace.xml
generated
106
.idea/workspace.xml
generated
@@ -9,84 +9,10 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="22c5bc80-996c-4846-b173-7dc8c2096fe3" name="Default" comment="fix: characters not on overriden charset would not print">
|
<list default="true" id="22c5bc80-996c-4846-b173-7dc8c2096fe3" name="Default" comment="why are you still looking for tga.gz">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/artifacts/TerrarumSansBitmap.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/artifacts/TerrarumSansBitmap.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/kotlinc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/kotlinc.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/libraries/KotlinJavaRuntime.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/libraries/KotlinJavaRuntime.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/BuildJAR_TerrarumSansBitmap.iml" beforeDir="false" afterPath="$PROJECT_DIR$/BuildJAR_TerrarumSansBitmap.iml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/FontTestGDX/FontTestGDX.iml" beforeDir="false" afterPath="$PROJECT_DIR$/FontTestGDX/FontTestGDX.iml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/FontTestGDX/src/FontTestGDX.kt" beforeDir="false" afterPath="$PROJECT_DIR$/FontTestGDX/src/FontTestGDX.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/FontTestGDX/src/TypewriterGDX.kt" beforeDir="false" afterPath="$PROJECT_DIR$/FontTestGDX/src/TypewriterGDX.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/LICENSE.md" beforeDir="false" afterPath="$PROJECT_DIR$/LICENSE.md" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/alphabetic_presentation_forms_extrawide_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/ascii_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/bengali_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/braille_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/cjkpunct_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/control_pictures_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/currencies_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/cyrilic_bulgarian_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/cyrilic_serbian_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/cyrilic_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/devanagari_internal_extrawide_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/devanagari_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/diacritical_marks_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/enclosed_alphanumeric_supplement_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/futhark.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/greek_polytonic_xyswap_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/greek_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/halfwidth_fullwidth_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/hangul_johab.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/hayeren_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/hentaigana_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/internal_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/ipa_ext_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/kana_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/kartuli_allcaps_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/kartuli_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/latinExtA_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/latinExtB_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/latinExtC_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/latinExtD_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/latinExt_additional_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/letterlike_symbols_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/phonetic_extensions_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/pua_codestyle_ascii_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/puae000-e0ff.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/richtext_furigana.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/sundanese_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/tamil_extrawide_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/thai_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/tsalagi_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/cr0.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/cr1.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/cr2.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/cr3.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/cr4.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/cr5.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/crlf.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/deadkey.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/movingkey.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/shiftin.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/shiftout.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/audio/space.wav" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/typewriter_intl_qwerty.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/typewriter/typewriter_ko_3set-390.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/unipunct_variable.tga" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/assets/wenquanyi.tga.gz" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/samples/wikipedia_x86.png" beforeDir="false" afterPath="$PROJECT_DIR$/samples/wikipedia_x86.png" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/net/torvald/terrarumtypewriterbitmap/gdx/TerrarumTypewriterBitmap.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/terrarum_sans_cyrillic_2.png" beforeDir="false" afterPath="$PROJECT_DIR$/terrarum_sans_cyrillic_2.png" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/work_files/typewriter_input/alphnum_glyphs_master.kra" beforeDir="false" afterPath="$PROJECT_DIR$/work_files/typewriter_input/alphnum_glyphs_master.kra" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/work_files/typewriter_input/alphnum_glyphs_resized.kra" beforeDir="false" afterPath="$PROJECT_DIR$/work_files/typewriter_input/alphnum_glyphs_resized.kra" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/work_files/typewriter_input/hangul_3set_glyphs_master.kra" beforeDir="false" afterPath="$PROJECT_DIR$/work_files/typewriter_input/hangul_3set_glyphs_master.kra" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/work_files/typewriter_input/typewriter_input_template.psd" beforeDir="false" afterPath="$PROJECT_DIR$/work_files/typewriter_input/typewriter_input_template.psd" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/work_files/typewriter_input/typewriter_intl_qwerty.psd" beforeDir="false" afterPath="$PROJECT_DIR$/work_files/typewriter_input/typewriter_intl_qwerty.psd" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/work_files/typewriter_input/typewriter_ko_3set-390.psd" beforeDir="false" afterPath="$PROJECT_DIR$/work_files/typewriter_input/typewriter_ko_3set-390.psd" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/work_files/typewriter_input/typewriter_ko_3set_glyphs_resized.kra" beforeDir="false" afterPath="$PROJECT_DIR$/work_files/typewriter_input/typewriter_ko_3set_glyphs_resized.kra" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -242,12 +168,6 @@
|
|||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
<configuration default="true" type="JetRunConfigurationType">
|
|
||||||
<module name="BuildJAR_TerrarumSansBitmap" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="Make" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
||||||
<option name="filePath" />
|
<option name="filePath" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
@@ -298,7 +218,23 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1726151824465</updated>
|
<updated>1726151824465</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="3" />
|
<task id="LOCAL-00003" summary="moving assets inside classpath">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1771460240293</created>
|
||||||
|
<option name="number" value="00003" />
|
||||||
|
<option name="presentableId" value="LOCAL-00003" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1771460240293</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00004" summary="why are you still looking for tga.gz">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1771551906182</created>
|
||||||
|
<option name="number" value="00004" />
|
||||||
|
<option name="presentableId" value="LOCAL-00004" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1771551906182</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="5" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TodoView">
|
<component name="TodoView">
|
||||||
@@ -324,7 +260,9 @@
|
|||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
<MESSAGE value="Old hangul rendering fix" />
|
<MESSAGE value="Old hangul rendering fix" />
|
||||||
<MESSAGE value="fix: characters not on overriden charset would not print" />
|
<MESSAGE value="fix: characters not on overriden charset would not print" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="fix: characters not on overriden charset would not print" />
|
<MESSAGE value="moving assets inside classpath" />
|
||||||
|
<MESSAGE value="why are you still looking for tga.gz" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="why are you still looking for tga.gz" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
26
OTFbuild/OTFbuild.iml
Normal file
26
OTFbuild/OTFbuild.iml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<output url="file://$MODULE_DIR$/out/production/OTFbuild" />
|
||||||
|
<output-test url="file://$MODULE_DIR$/out/test/OTFbuild" />
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/out" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/bitsnpicas_runtime/BitsNPicas.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES>
|
||||||
|
<root url="file://$MODULE_DIR$/bitsnpicas_source_codes/src" />
|
||||||
|
</SOURCES>
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
80
OTFbuild/build_otf.sh
Executable file
80
OTFbuild/build_otf.sh
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
ASSETS_DIR="$PROJECT_DIR/src/assets"
|
||||||
|
OUTPUT_DIR="$SCRIPT_DIR"
|
||||||
|
BITSNPICAS_JAR="$SCRIPT_DIR/bitsnpicas_runtime/BitsNPicas.jar"
|
||||||
|
|
||||||
|
# Output paths
|
||||||
|
KBITX_OUTPUT="$OUTPUT_DIR/TerrarumSansBitmap.kbitx"
|
||||||
|
TTF_OUTPUT="$OUTPUT_DIR/TerrarumSansBitmap.ttf"
|
||||||
|
|
||||||
|
echo "=== Terrarum Sans Bitmap OTF Build Pipeline ==="
|
||||||
|
echo "Project: $PROJECT_DIR"
|
||||||
|
echo "Assets: $ASSETS_DIR"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 1: Compile the builder
|
||||||
|
echo "--- Step 1: Compiling OTFbuild module ---"
|
||||||
|
COMPILE_CLASSPATH="$BITSNPICAS_JAR"
|
||||||
|
SRC_DIR="$SCRIPT_DIR/src"
|
||||||
|
OUT_DIR="$SCRIPT_DIR/out"
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
# Find all Kotlin source files
|
||||||
|
SRC_FILES=$(find "$SRC_DIR" -name "*.kt" | tr '\n' ' ')
|
||||||
|
|
||||||
|
# Try to find Kotlin compiler
|
||||||
|
if command -v kotlinc &> /dev/null; then
|
||||||
|
KOTLINC="kotlinc"
|
||||||
|
KOTLIN_STDLIB=""
|
||||||
|
else
|
||||||
|
# Try IntelliJ's bundled Kotlin
|
||||||
|
IDEA_CACHE="$HOME/.cache/JetBrains"
|
||||||
|
KOTLIN_DIST=$(find "$IDEA_CACHE" -path "*/kotlin-dist-for-ide/*/lib/kotlin-compiler.jar" 2>/dev/null | sort -V | tail -1)
|
||||||
|
if [ -n "$KOTLIN_DIST" ]; then
|
||||||
|
KOTLIN_LIB="$(dirname "$KOTLIN_DIST")"
|
||||||
|
KOTLINC_CP="$KOTLIN_LIB/kotlin-compiler.jar:$KOTLIN_LIB/kotlin-stdlib.jar:$KOTLIN_LIB/trove4j.jar:$KOTLIN_LIB/kotlin-reflect.jar:$KOTLIN_LIB/kotlin-script-runtime.jar:$KOTLIN_LIB/kotlin-daemon.jar:$KOTLIN_LIB/annotations-13.0.jar"
|
||||||
|
KOTLIN_STDLIB="$KOTLIN_LIB/kotlin-stdlib.jar:$KOTLIN_LIB/kotlin-stdlib-jdk7.jar:$KOTLIN_LIB/kotlin-stdlib-jdk8.jar"
|
||||||
|
echo "Using IntelliJ's Kotlin from: $KOTLIN_LIB"
|
||||||
|
else
|
||||||
|
echo "ERROR: kotlinc not found. Please install Kotlin compiler or build via IntelliJ IDEA."
|
||||||
|
echo ""
|
||||||
|
echo "Alternative: Build the OTFbuild module in IntelliJ IDEA, then run:"
|
||||||
|
echo " java -cp \"$OUT_DIR:$COMPILE_CLASSPATH\" net.torvald.otfbuild.MainKt \"$ASSETS_DIR\" \"$KBITX_OUTPUT\""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$KOTLIN_STDLIB" ]; then
|
||||||
|
# Use IntelliJ's bundled Kotlin via java
|
||||||
|
java -cp "$KOTLINC_CP" org.jetbrains.kotlin.cli.jvm.K2JVMCompiler \
|
||||||
|
-cp "$COMPILE_CLASSPATH:$KOTLIN_STDLIB" -d "$OUT_DIR" $SRC_FILES
|
||||||
|
else
|
||||||
|
kotlinc -cp "$COMPILE_CLASSPATH" -d "$OUT_DIR" $SRC_FILES
|
||||||
|
KOTLIN_STDLIB=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 2: Run the builder to generate KBITX
|
||||||
|
echo ""
|
||||||
|
echo "--- Step 2: Generating KBITX ---"
|
||||||
|
RUNTIME_CP="$OUT_DIR:$COMPILE_CLASSPATH"
|
||||||
|
if [ -n "$KOTLIN_STDLIB" ]; then
|
||||||
|
RUNTIME_CP="$RUNTIME_CP:$KOTLIN_STDLIB"
|
||||||
|
fi
|
||||||
|
java -cp "$RUNTIME_CP" net.torvald.otfbuild.MainKt "$ASSETS_DIR" "$KBITX_OUTPUT"
|
||||||
|
|
||||||
|
# Step 3: Convert KBITX to TTF via BitsNPicas
|
||||||
|
echo ""
|
||||||
|
echo "--- Step 3: Converting KBITX to TTF ---"
|
||||||
|
java -jar "$BITSNPICAS_JAR" convertbitmap \
|
||||||
|
-f ttf -o "$TTF_OUTPUT" \
|
||||||
|
"$KBITX_OUTPUT"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Build complete ==="
|
||||||
|
echo " KBITX: $KBITX_OUTPUT"
|
||||||
|
echo " TTF: $TTF_OUTPUT"
|
||||||
133
OTFbuild/src/net/torvald/otfbuild/DevanagariTamilProcessor.kt
Normal file
133
OTFbuild/src/net/torvald/otfbuild/DevanagariTamilProcessor.kt
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package net.torvald.otfbuild
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures all Devanagari, Tamil, Sundanese, and Alphabetic Presentation Forms
|
||||||
|
* PUA glyphs are included in the font. Since BitsNPicas doesn't support OpenType
|
||||||
|
* GSUB/GPOS features, complex text shaping must be done by the application.
|
||||||
|
*
|
||||||
|
* All the relevant PUA codepoints are already in the sprite sheets and extracted
|
||||||
|
* by GlyphSheetParser. This processor:
|
||||||
|
* 1. Verifies that key PUA ranges have been loaded
|
||||||
|
* 2. Ensures Unicode pre-composed forms (U+0958–U+095F) map correctly
|
||||||
|
* 3. Documents the mapping for reference
|
||||||
|
*
|
||||||
|
* The runtime normalise() function handles the actual Unicode → PUA mapping,
|
||||||
|
* but since we can't put GSUB tables into the KBITX/TTF, applications must
|
||||||
|
* use the PUA codepoints directly, or perform their own normalisation.
|
||||||
|
*/
|
||||||
|
class DevanagariTamilProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that key PUA glyphs exist in the extracted set.
|
||||||
|
* Returns a set of codepoints that should be included but are missing.
|
||||||
|
*/
|
||||||
|
fun verify(glyphs: Map<Int, ExtractedGlyph>): Set<Int> {
|
||||||
|
val missing = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
// Devanagari special syllables
|
||||||
|
val devanagariSpecials = listOf(
|
||||||
|
0xF0100, // Ru
|
||||||
|
0xF0101, // Ruu
|
||||||
|
0xF0102, // RRu
|
||||||
|
0xF0103, // RRuu
|
||||||
|
0xF0104, // Hu
|
||||||
|
0xF0105, // Huu
|
||||||
|
0xF0106, // RYA
|
||||||
|
0xF0107, // Half-RYA
|
||||||
|
0xF0108, // Open YA
|
||||||
|
0xF0109, // Open Half-YA
|
||||||
|
0xF010B, // Eyelash RA
|
||||||
|
0xF010C, // RA superscript
|
||||||
|
0xF010D, // RA superscript (complex)
|
||||||
|
0xF010E, // DDRA (Marwari)
|
||||||
|
0xF010F, // Alt Half SHA
|
||||||
|
)
|
||||||
|
|
||||||
|
// Devanagari presentation consonants (full forms)
|
||||||
|
val devaPresentation = (0xF0140..0xF022F).toList()
|
||||||
|
// Devanagari presentation consonants (half forms)
|
||||||
|
val devaHalf = (0xF0230..0xF031F).toList()
|
||||||
|
// Devanagari presentation consonants (with RA)
|
||||||
|
val devaRa = (0xF0320..0xF040F).toList()
|
||||||
|
// Devanagari presentation consonants (with RA, half forms)
|
||||||
|
val devaRaHalf = (0xF0410..0xF04FF).toList()
|
||||||
|
|
||||||
|
// Devanagari II variant forms
|
||||||
|
val devaII = (0xF0110..0xF012F).toList()
|
||||||
|
|
||||||
|
// Devanagari named ligatures
|
||||||
|
val devaLigatures = listOf(
|
||||||
|
0xF01A1, // K.SS
|
||||||
|
0xF01A2, // J.NY
|
||||||
|
0xF01A3, // T.T
|
||||||
|
0xF01A4, // N.T
|
||||||
|
0xF01A5, // N.N
|
||||||
|
0xF01A6, // S.V
|
||||||
|
0xF01A7, // SS.P
|
||||||
|
0xF01A8, // SH.C
|
||||||
|
0xF01A9, // SH.N
|
||||||
|
0xF01AA, // SH.V
|
||||||
|
0xF01AB, // J.Y
|
||||||
|
0xF01AC, // J.J.Y
|
||||||
|
0xF01BC, // K.T
|
||||||
|
// D-series ligatures
|
||||||
|
0xF01B0, 0xF01B1, 0xF01B2, 0xF01B3, 0xF01B4,
|
||||||
|
0xF01B5, 0xF01B6, 0xF01B7, 0xF01B8, 0xF01B9,
|
||||||
|
// Marwari
|
||||||
|
0xF01BA, 0xF01BB,
|
||||||
|
// Extended ligatures
|
||||||
|
0xF01BD, 0xF01BE, 0xF01BF,
|
||||||
|
0xF01C0, 0xF01C1, 0xF01C2, 0xF01C3, 0xF01C4, 0xF01C5,
|
||||||
|
0xF01C6, 0xF01C7, 0xF01C8, 0xF01C9, 0xF01CA, 0xF01CB,
|
||||||
|
0xF01CD, 0xF01CE, 0xF01CF,
|
||||||
|
0xF01D0, 0xF01D1, 0xF01D2, 0xF01D3, 0xF01D4, 0xF01D5,
|
||||||
|
0xF01D6, 0xF01D7, 0xF01D8, 0xF01D9, 0xF01DA,
|
||||||
|
0xF01DB, 0xF01DC, 0xF01DD, 0xF01DE, 0xF01DF,
|
||||||
|
0xF01E0, 0xF01E1, 0xF01E2, 0xF01E3,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tamil ligatures
|
||||||
|
val tamilLigatures = listOf(
|
||||||
|
0xF00C0, 0xF00C1, // TTA+I, TTA+II
|
||||||
|
0xF00ED, // KSSA
|
||||||
|
0xF00EE, // SHRII
|
||||||
|
0xF00F0, 0xF00F1, 0xF00F2, 0xF00F3, 0xF00F4, 0xF00F5, // consonant+I
|
||||||
|
) + (0xF00C2..0xF00D3).toList() + // consonant+U
|
||||||
|
(0xF00D4..0xF00E5).toList() // consonant+UU
|
||||||
|
|
||||||
|
// Sundanese internal forms
|
||||||
|
val sundanese = listOf(
|
||||||
|
0xF0500, // ING
|
||||||
|
0xF0501, // ENG
|
||||||
|
0xF0502, // EUNG
|
||||||
|
0xF0503, // IR
|
||||||
|
0xF0504, // ER
|
||||||
|
0xF0505, // EUR
|
||||||
|
0xF0506, // LU
|
||||||
|
)
|
||||||
|
|
||||||
|
// Alphabetic Presentation Forms (already in sheet 38)
|
||||||
|
// FB00–FB06 (Latin ligatures), FB13–FB17 (Armenian ligatures)
|
||||||
|
|
||||||
|
// Check all expected ranges
|
||||||
|
val allExpected = devanagariSpecials + devaPresentation + devaHalf + devaRa + devaRaHalf +
|
||||||
|
devaII + devaLigatures + tamilLigatures + sundanese
|
||||||
|
|
||||||
|
for (cp in allExpected) {
|
||||||
|
if (!glyphs.containsKey(cp)) {
|
||||||
|
missing.add(cp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing.isNotEmpty()) {
|
||||||
|
println(" [DevanagariTamilProcessor] ${missing.size} expected PUA glyphs missing")
|
||||||
|
// Only warn for the first few
|
||||||
|
missing.take(10).forEach { println(" Missing: U+${it.toString(16).uppercase().padStart(5, '0')}") }
|
||||||
|
if (missing.size > 10) println(" ... and ${missing.size - 10} more")
|
||||||
|
} else {
|
||||||
|
println(" [DevanagariTamilProcessor] All expected PUA glyphs present")
|
||||||
|
}
|
||||||
|
|
||||||
|
return missing
|
||||||
|
}
|
||||||
|
}
|
||||||
321
OTFbuild/src/net/torvald/otfbuild/GlyphSheetParser.kt
Normal file
321
OTFbuild/src/net/torvald/otfbuild/GlyphSheetParser.kt
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
package net.torvald.otfbuild
|
||||||
|
|
||||||
|
import com.kreative.bitsnpicas.BitmapFontGlyph
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Glyph properties extracted from tag column.
|
||||||
|
* Mirrors GlyphProps from the runtime but is standalone.
|
||||||
|
*/
|
||||||
|
data class ExtractedGlyphProps(
|
||||||
|
val width: Int,
|
||||||
|
val isLowHeight: Boolean = false,
|
||||||
|
val nudgeX: Int = 0,
|
||||||
|
val nudgeY: Int = 0,
|
||||||
|
val alignWhere: Int = 0,
|
||||||
|
val writeOnTop: Int = -1,
|
||||||
|
val stackWhere: Int = 0,
|
||||||
|
val hasKernData: Boolean = false,
|
||||||
|
val isKernYtype: Boolean = false,
|
||||||
|
val kerningMask: Int = 255,
|
||||||
|
val directiveOpcode: Int = 0,
|
||||||
|
val directiveArg1: Int = 0,
|
||||||
|
val directiveArg2: Int = 0,
|
||||||
|
val extInfo: IntArray = IntArray(15),
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val ALIGN_LEFT = 0
|
||||||
|
const val ALIGN_RIGHT = 1
|
||||||
|
const val ALIGN_CENTRE = 2
|
||||||
|
const val ALIGN_BEFORE = 3
|
||||||
|
|
||||||
|
const val STACK_UP = 0
|
||||||
|
const val STACK_DOWN = 1
|
||||||
|
const val STACK_BEFORE_N_AFTER = 2
|
||||||
|
const val STACK_UP_N_DOWN = 3
|
||||||
|
const val STACK_DONT = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requiredExtInfoCount(): Int =
|
||||||
|
if (stackWhere == STACK_BEFORE_N_AFTER) 2
|
||||||
|
else if (directiveOpcode in 0b10000_000..0b10000_111) 7
|
||||||
|
else 0
|
||||||
|
|
||||||
|
fun isPragma(pragma: String) = when (pragma) {
|
||||||
|
"replacewith" -> directiveOpcode in 0b10000_000..0b10000_111
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
val isIllegal: Boolean get() = directiveOpcode == 255
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ExtractedGlyph(
|
||||||
|
val codepoint: Int,
|
||||||
|
val props: ExtractedGlyphProps,
|
||||||
|
val bitmap: Array<ByteArray>, // [row][col], 0 or -1(0xFF)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts glyph bitmaps and properties from TGA sprite sheets.
|
||||||
|
* Ported from TerrarumSansBitmap.buildWidthTable() and related methods.
|
||||||
|
*/
|
||||||
|
class GlyphSheetParser(private val assetsDir: String) {
|
||||||
|
|
||||||
|
private fun Boolean.toInt() = if (this) 1 else 0
|
||||||
|
/** @return 32-bit number: if alpha channel is zero, return 0; else return the original value */
|
||||||
|
private fun Int.tagify() = if (this and 0xFF == 0) 0 else this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse all sheets and return a map of codepoint -> (props, bitmap).
|
||||||
|
*/
|
||||||
|
fun parseAll(): Map<Int, ExtractedGlyph> {
|
||||||
|
val result = HashMap<Int, ExtractedGlyph>(65536)
|
||||||
|
|
||||||
|
SheetConfig.fileList.forEachIndexed { sheetIndex, filename ->
|
||||||
|
val file = File(assetsDir, filename)
|
||||||
|
if (!file.exists()) {
|
||||||
|
println(" [SKIP] $filename not found")
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
|
||||||
|
val isVariable = SheetConfig.isVariable(filename)
|
||||||
|
val isXYSwapped = SheetConfig.isXYSwapped(filename)
|
||||||
|
val isExtraWide = SheetConfig.isExtraWide(filename)
|
||||||
|
val cellW = SheetConfig.getCellWidth(sheetIndex)
|
||||||
|
val cellH = SheetConfig.getCellHeight(sheetIndex)
|
||||||
|
val cols = SheetConfig.getColumns(sheetIndex)
|
||||||
|
|
||||||
|
val image = TgaReader.read(file)
|
||||||
|
|
||||||
|
val statusParts = mutableListOf<String>()
|
||||||
|
if (isVariable) statusParts.add("VARIABLE")
|
||||||
|
if (isXYSwapped) statusParts.add("XYSWAP")
|
||||||
|
if (isExtraWide) statusParts.add("EXTRAWIDE")
|
||||||
|
if (statusParts.isEmpty()) statusParts.add("STATIC")
|
||||||
|
println(" Loading [${statusParts.joinToString()}] $filename")
|
||||||
|
|
||||||
|
if (isVariable) {
|
||||||
|
parseVariableSheet(image, sheetIndex, cellW, cellH, cols, isXYSwapped, result)
|
||||||
|
} else {
|
||||||
|
parseFixedSheet(image, sheetIndex, cellW, cellH, cols, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add fixed-width overrides
|
||||||
|
addFixedWidthOverrides(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a variable-width sheet: extract tag column for properties, bitmap for glyph.
|
||||||
|
*/
|
||||||
|
private fun parseVariableSheet(
|
||||||
|
image: TgaImage,
|
||||||
|
sheetIndex: Int,
|
||||||
|
cellW: Int,
|
||||||
|
cellH: Int,
|
||||||
|
cols: Int,
|
||||||
|
isXYSwapped: Boolean,
|
||||||
|
result: HashMap<Int, ExtractedGlyph>
|
||||||
|
) {
|
||||||
|
val codeRangeList = SheetConfig.codeRange[sheetIndex]
|
||||||
|
val binaryCodeOffset = cellW - 1 // tag column is last pixel column of cell
|
||||||
|
|
||||||
|
codeRangeList.forEachIndexed { index, code ->
|
||||||
|
val cellX: Int
|
||||||
|
val cellY: Int
|
||||||
|
|
||||||
|
if (isXYSwapped) {
|
||||||
|
cellX = (index / cols) * cellW // row becomes X
|
||||||
|
cellY = (index % cols) * cellH // col becomes Y
|
||||||
|
} else {
|
||||||
|
cellX = (index % cols) * cellW
|
||||||
|
cellY = (index / cols) * cellH
|
||||||
|
}
|
||||||
|
|
||||||
|
val codeStartX = cellX + binaryCodeOffset
|
||||||
|
val codeStartY = cellY
|
||||||
|
|
||||||
|
// Parse tag column
|
||||||
|
val width = (0..4).fold(0) { acc, y ->
|
||||||
|
acc or ((image.getPixel(codeStartX, codeStartY + y).and(0xFF) != 0).toInt() shl y)
|
||||||
|
}
|
||||||
|
val isLowHeight = image.getPixel(codeStartX, codeStartY + 5).and(0xFF) != 0
|
||||||
|
|
||||||
|
// Kerning data
|
||||||
|
val kerningBit1 = image.getPixel(codeStartX, codeStartY + 6).tagify()
|
||||||
|
val kerningBit2 = image.getPixel(codeStartX, codeStartY + 7).tagify()
|
||||||
|
val kerningBit3 = image.getPixel(codeStartX, codeStartY + 8).tagify()
|
||||||
|
var isKernYtype = (kerningBit1 and 0x80000000.toInt()) != 0
|
||||||
|
var kerningMask = kerningBit1.ushr(8).and(0xFFFFFF)
|
||||||
|
val hasKernData = kerningBit1 and 0xFF != 0
|
||||||
|
if (!hasKernData) {
|
||||||
|
isKernYtype = false
|
||||||
|
kerningMask = 255
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compiler directives
|
||||||
|
val compilerDirectives = image.getPixel(codeStartX, codeStartY + 9).tagify()
|
||||||
|
val directiveOpcode = compilerDirectives.ushr(24).and(255)
|
||||||
|
val directiveArg1 = compilerDirectives.ushr(16).and(255)
|
||||||
|
val directiveArg2 = compilerDirectives.ushr(8).and(255)
|
||||||
|
|
||||||
|
// Nudge
|
||||||
|
val nudgingBits = image.getPixel(codeStartX, codeStartY + 10).tagify()
|
||||||
|
val nudgeX = nudgingBits.ushr(24).toByte().toInt()
|
||||||
|
val nudgeY = nudgingBits.ushr(16).toByte().toInt()
|
||||||
|
|
||||||
|
// Diacritics anchors (we don't store them in ExtractedGlyphProps for now but could)
|
||||||
|
// For alignment and width, they are useful during composition but not in final output
|
||||||
|
|
||||||
|
// Alignment
|
||||||
|
val alignWhere = (0..1).fold(0) { acc, y ->
|
||||||
|
acc or ((image.getPixel(codeStartX, codeStartY + y + 15).and(0xFF) != 0).toInt() shl y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write on top
|
||||||
|
var writeOnTop = image.getPixel(codeStartX, codeStartY + 17) // NO .tagify()
|
||||||
|
if (writeOnTop and 0xFF == 0) writeOnTop = -1
|
||||||
|
else {
|
||||||
|
writeOnTop = if (writeOnTop.ushr(8) == 0xFFFFFF) 0 else writeOnTop.ushr(28) and 15
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack where
|
||||||
|
val stackWhere0 = image.getPixel(codeStartX, codeStartY + 18).tagify()
|
||||||
|
val stackWhere1 = image.getPixel(codeStartX, codeStartY + 19).tagify()
|
||||||
|
val stackWhere = if (stackWhere0 == 0x00FF00FF && stackWhere1 == 0x00FF00FF)
|
||||||
|
ExtractedGlyphProps.STACK_DONT
|
||||||
|
else (0..1).fold(0) { acc, y ->
|
||||||
|
acc or ((image.getPixel(codeStartX, codeStartY + y + 18).and(0xFF) != 0).toInt() shl y)
|
||||||
|
}
|
||||||
|
|
||||||
|
val extInfo = IntArray(15)
|
||||||
|
val props = ExtractedGlyphProps(
|
||||||
|
width, isLowHeight, nudgeX, nudgeY, alignWhere, writeOnTop, stackWhere,
|
||||||
|
hasKernData, isKernYtype, kerningMask, directiveOpcode, directiveArg1, directiveArg2, extInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse extInfo if needed
|
||||||
|
val extCount = props.requiredExtInfoCount()
|
||||||
|
if (extCount > 0) {
|
||||||
|
for (x in 0 until extCount) {
|
||||||
|
var info = 0
|
||||||
|
for (y in 0..19) {
|
||||||
|
if (image.getPixel(cellX + x, cellY + y).and(0xFF) != 0) {
|
||||||
|
info = info or (1 shl y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extInfo[x] = info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract glyph bitmap: all pixels in cell except tag column
|
||||||
|
val bitmapW = cellW - 1 // exclude tag column
|
||||||
|
val bitmap = Array(cellH) { row ->
|
||||||
|
ByteArray(bitmapW) { col ->
|
||||||
|
val px = image.getPixel(cellX + col, cellY + row)
|
||||||
|
if (px and 0xFF != 0) 0xFF.toByte() else 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result[code] = ExtractedGlyph(code, props, bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a fixed-width sheet (Hangul, Unihan, Runic, Custom Sym).
|
||||||
|
*/
|
||||||
|
private fun parseFixedSheet(
|
||||||
|
image: TgaImage,
|
||||||
|
sheetIndex: Int,
|
||||||
|
cellW: Int,
|
||||||
|
cellH: Int,
|
||||||
|
cols: Int,
|
||||||
|
result: HashMap<Int, ExtractedGlyph>
|
||||||
|
) {
|
||||||
|
val codeRangeList = SheetConfig.codeRange[sheetIndex]
|
||||||
|
val fixedWidth = when (sheetIndex) {
|
||||||
|
SheetConfig.SHEET_CUSTOM_SYM -> 20
|
||||||
|
SheetConfig.SHEET_HANGUL -> SheetConfig.W_HANGUL_BASE
|
||||||
|
SheetConfig.SHEET_RUNIC -> 9
|
||||||
|
SheetConfig.SHEET_UNIHAN -> SheetConfig.W_UNIHAN
|
||||||
|
else -> cellW
|
||||||
|
}
|
||||||
|
|
||||||
|
codeRangeList.forEachIndexed { index, code ->
|
||||||
|
val cellX = (index % cols) * cellW
|
||||||
|
val cellY = (index / cols) * cellH
|
||||||
|
|
||||||
|
val bitmap = Array(cellH) { row ->
|
||||||
|
ByteArray(cellW) { col ->
|
||||||
|
val px = image.getPixel(cellX + col, cellY + row)
|
||||||
|
if (px and 0xFF != 0) 0xFF.toByte() else 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val props = ExtractedGlyphProps(fixedWidth)
|
||||||
|
result[code] = ExtractedGlyph(code, props, bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply fixed-width overrides as in buildWidthTableFixed().
|
||||||
|
*/
|
||||||
|
private fun addFixedWidthOverrides(result: HashMap<Int, ExtractedGlyph>) {
|
||||||
|
// Hangul compat jamo
|
||||||
|
SheetConfig.codeRangeHangulCompat.forEach { code ->
|
||||||
|
if (!result.containsKey(code)) {
|
||||||
|
result[code] = ExtractedGlyph(code, ExtractedGlyphProps(SheetConfig.W_HANGUL_BASE), emptyBitmap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero-width ranges
|
||||||
|
(0xD800..0xDFFF).forEach { result[it] = ExtractedGlyph(it, ExtractedGlyphProps(0), emptyBitmap()) }
|
||||||
|
(0x100000..0x10FFFF).forEach { result[it] = ExtractedGlyph(it, ExtractedGlyphProps(0), emptyBitmap()) }
|
||||||
|
(0xFFFA0..0xFFFFF).forEach { result[it] = ExtractedGlyph(it, ExtractedGlyphProps(0), emptyBitmap()) }
|
||||||
|
|
||||||
|
// Insular letter
|
||||||
|
result[0x1D79]?.let { /* already in sheet */ } ?: run {
|
||||||
|
result[0x1D79] = ExtractedGlyph(0x1D79, ExtractedGlyphProps(9), emptyBitmap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replacement character at U+007F
|
||||||
|
result[0x7F]?.let { existing ->
|
||||||
|
result[0x7F] = existing.copy(props = existing.props.copy(width = 15))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null char
|
||||||
|
result[0] = ExtractedGlyph(0, ExtractedGlyphProps(0), emptyBitmap())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emptyBitmap() = Array(SheetConfig.H) { ByteArray(SheetConfig.W_VAR_INIT) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts raw Hangul jamo bitmaps from the Hangul sheet for composition.
|
||||||
|
* Returns a function: (index, row) -> bitmap
|
||||||
|
*/
|
||||||
|
fun getHangulJamoBitmaps(): (Int, Int) -> Array<ByteArray> {
|
||||||
|
val filename = SheetConfig.fileList[SheetConfig.SHEET_HANGUL]
|
||||||
|
val file = File(assetsDir, filename)
|
||||||
|
if (!file.exists()) {
|
||||||
|
println(" [WARNING] Hangul sheet not found")
|
||||||
|
return { _, _ -> Array(SheetConfig.H) { ByteArray(SheetConfig.W_HANGUL_BASE) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
val image = TgaReader.read(file)
|
||||||
|
val cellW = SheetConfig.W_HANGUL_BASE
|
||||||
|
val cellH = SheetConfig.H
|
||||||
|
|
||||||
|
return { index: Int, row: Int ->
|
||||||
|
val cellX = index * cellW
|
||||||
|
val cellY = row * cellH
|
||||||
|
Array(cellH) { r ->
|
||||||
|
ByteArray(cellW) { c ->
|
||||||
|
val px = image.getPixel(cellX + c, cellY + r)
|
||||||
|
if (px and 0xFF != 0) 0xFF.toByte() else 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
OTFbuild/src/net/torvald/otfbuild/HangulCompositor.kt
Normal file
124
OTFbuild/src/net/torvald/otfbuild/HangulCompositor.kt
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package net.torvald.otfbuild
|
||||||
|
|
||||||
|
import com.kreative.bitsnpicas.BitmapFontGlyph
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composes 11,172 Hangul syllables (U+AC00–U+D7A3) from jamo sprite pieces.
|
||||||
|
* Also composes Hangul Compatibility Jamo (U+3130–U+318F).
|
||||||
|
*
|
||||||
|
* Ported from TerrarumSansBitmap.kt Hangul assembly logic.
|
||||||
|
*/
|
||||||
|
class HangulCompositor(private val parser: GlyphSheetParser) {
|
||||||
|
|
||||||
|
private val getJamoBitmap = parser.getHangulJamoBitmaps()
|
||||||
|
private val cellW = SheetConfig.W_HANGUL_BASE
|
||||||
|
private val cellH = SheetConfig.H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose all Hangul syllables and compatibility jamo.
|
||||||
|
* @return Map of codepoint to BitmapFontGlyph
|
||||||
|
*/
|
||||||
|
fun compose(): Map<Int, Pair<BitmapFontGlyph, Int>> {
|
||||||
|
val result = HashMap<Int, Pair<BitmapFontGlyph, Int>>(12000)
|
||||||
|
|
||||||
|
// Compose Hangul Compatibility Jamo (U+3130–U+318F)
|
||||||
|
// These are standalone jamo from row 0 of the sheet
|
||||||
|
for (c in 0x3130..0x318F) {
|
||||||
|
val index = c - 0x3130
|
||||||
|
val bitmap = getJamoBitmap(index, 0)
|
||||||
|
val glyph = bitmapToGlyph(bitmap, cellW, cellH)
|
||||||
|
result[c] = glyph to cellW
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose 11,172 Hangul syllables (U+AC00–U+D7A3)
|
||||||
|
println(" Composing 11,172 Hangul syllables...")
|
||||||
|
for (c in 0xAC00..0xD7A3) {
|
||||||
|
val cInt = c - 0xAC00
|
||||||
|
val indexCho = cInt / (SheetConfig.JUNG_COUNT * SheetConfig.JONG_COUNT)
|
||||||
|
val indexJung = cInt / SheetConfig.JONG_COUNT % SheetConfig.JUNG_COUNT
|
||||||
|
val indexJong = cInt % SheetConfig.JONG_COUNT // 0 = no jongseong
|
||||||
|
|
||||||
|
// Map to jamo codepoints
|
||||||
|
val choCP = 0x1100 + indexCho
|
||||||
|
val jungCP = 0x1161 + indexJung
|
||||||
|
val jongCP = if (indexJong > 0) 0x11A8 + indexJong - 1 else 0
|
||||||
|
|
||||||
|
// Get sheet indices
|
||||||
|
val iCho = SheetConfig.toHangulChoseongIndex(choCP)
|
||||||
|
val iJung = SheetConfig.toHangulJungseongIndex(jungCP) ?: 0
|
||||||
|
val iJong = if (jongCP != 0) SheetConfig.toHangulJongseongIndex(jongCP) ?: 0 else 0
|
||||||
|
|
||||||
|
// Get row positions
|
||||||
|
val choRow = SheetConfig.getHanInitialRow(iCho, iJung, iJong)
|
||||||
|
val jungRow = SheetConfig.getHanMedialRow(iCho, iJung, iJong)
|
||||||
|
val jongRow = SheetConfig.getHanFinalRow(iCho, iJung, iJong)
|
||||||
|
|
||||||
|
// Get jamo bitmaps
|
||||||
|
val choBitmap = getJamoBitmap(iCho, choRow)
|
||||||
|
val jungBitmap = getJamoBitmap(iJung, jungRow)
|
||||||
|
|
||||||
|
// Compose
|
||||||
|
val composed = composeBitmaps(choBitmap, jungBitmap, cellW, cellH)
|
||||||
|
if (indexJong > 0) {
|
||||||
|
val jongBitmap = getJamoBitmap(iJong, jongRow)
|
||||||
|
composeBitmapInto(composed, jongBitmap, cellW, cellH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine advance width
|
||||||
|
val advanceWidth = if (iJung in SheetConfig.hangulPeaksWithExtraWidth) cellW + 1 else cellW
|
||||||
|
|
||||||
|
val glyph = bitmapToGlyph(composed, advanceWidth, cellH)
|
||||||
|
result[c] = glyph to advanceWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
println(" Hangul composition done: ${result.size} glyphs")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose two bitmaps by OR-ing them together.
|
||||||
|
*/
|
||||||
|
private fun composeBitmaps(a: Array<ByteArray>, b: Array<ByteArray>, w: Int, h: Int): Array<ByteArray> {
|
||||||
|
val result = Array(h) { row ->
|
||||||
|
ByteArray(w) { col ->
|
||||||
|
val av = a.getOrNull(row)?.getOrNull(col)?.toInt()?.and(0xFF) ?: 0
|
||||||
|
val bv = b.getOrNull(row)?.getOrNull(col)?.toInt()?.and(0xFF) ?: 0
|
||||||
|
if (av != 0 || bv != 0) 0xFF.toByte() else 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OR a bitmap into an existing one.
|
||||||
|
*/
|
||||||
|
private fun composeBitmapInto(target: Array<ByteArray>, source: Array<ByteArray>, w: Int, h: Int) {
|
||||||
|
for (row in 0 until minOf(h, target.size, source.size)) {
|
||||||
|
for (col in 0 until minOf(w, target[row].size, source[row].size)) {
|
||||||
|
if (source[row][col].toInt() and 0xFF != 0) {
|
||||||
|
target[row][col] = 0xFF.toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Convert a byte[][] bitmap to BitmapFontGlyph.
|
||||||
|
*/
|
||||||
|
fun bitmapToGlyph(bitmap: Array<ByteArray>, advanceWidth: Int, cellH: Int): BitmapFontGlyph {
|
||||||
|
val h = bitmap.size
|
||||||
|
val w = if (h > 0) bitmap[0].size else 0
|
||||||
|
val glyphData = Array(h) { row ->
|
||||||
|
ByteArray(w) { col -> bitmap[row][col] }
|
||||||
|
}
|
||||||
|
// BitmapFontGlyph(byte[][] glyph, int offset, int width, int ascent)
|
||||||
|
// offset = x offset (left side bearing), width = advance width, ascent = baseline from top
|
||||||
|
val glyph = BitmapFontGlyph()
|
||||||
|
glyph.setGlyph(glyphData)
|
||||||
|
glyph.setXY(0, cellH) // y = ascent from top of em square to baseline
|
||||||
|
glyph.setCharacterWidth(advanceWidth)
|
||||||
|
return glyph
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
218
OTFbuild/src/net/torvald/otfbuild/KbitxBuilder.kt
Normal file
218
OTFbuild/src/net/torvald/otfbuild/KbitxBuilder.kt
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
package net.torvald.otfbuild
|
||||||
|
|
||||||
|
import com.kreative.bitsnpicas.BitmapFont
|
||||||
|
import com.kreative.bitsnpicas.BitmapFontGlyph
|
||||||
|
import com.kreative.bitsnpicas.Font
|
||||||
|
import com.kreative.bitsnpicas.exporter.KbitxBitmapFontExporter
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orchestrates the entire font building pipeline:
|
||||||
|
* 1. Parse all TGA sheets
|
||||||
|
* 2. Create BitmapFont with metrics
|
||||||
|
* 3. Add all extracted glyphs
|
||||||
|
* 4. Compose Hangul syllables
|
||||||
|
* 5. Verify Devanagari/Tamil PUA glyphs
|
||||||
|
* 6. Generate kerning pairs
|
||||||
|
* 7. Export to KBITX
|
||||||
|
*/
|
||||||
|
class KbitxBuilder(private val assetsDir: String) {
|
||||||
|
|
||||||
|
fun build(outputPath: String) {
|
||||||
|
println("=== Terrarum Sans Bitmap OTF Builder ===")
|
||||||
|
println("Assets: $assetsDir")
|
||||||
|
println("Output: $outputPath")
|
||||||
|
println()
|
||||||
|
|
||||||
|
// 1. Create BitmapFont with metrics
|
||||||
|
println("[1/7] Creating BitmapFont...")
|
||||||
|
val font = BitmapFont(
|
||||||
|
16, // emAscent: baseline to top of em square
|
||||||
|
4, // emDescent: baseline to bottom of em square
|
||||||
|
16, // lineAscent
|
||||||
|
4, // lineDescent
|
||||||
|
8, // xHeight
|
||||||
|
12, // capHeight
|
||||||
|
0 // lineGap
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set font names
|
||||||
|
font.setName(Font.NAME_FAMILY, "Terrarum Sans Bitmap")
|
||||||
|
font.setName(Font.NAME_STYLE, "Regular")
|
||||||
|
font.setName(Font.NAME_VERSION, "Version 1.0")
|
||||||
|
font.setName(Font.NAME_FAMILY_AND_STYLE, "Terrarum Sans Bitmap Regular")
|
||||||
|
font.setName(Font.NAME_COPYRIGHT, "Copyright (c) 2017-2026 see CONTRIBUTORS.txt")
|
||||||
|
font.setName(Font.NAME_DESCRIPTION, "Bitmap font for Terrarum game engine")
|
||||||
|
font.setName(Font.NAME_LICENSE_DESCRIPTION, "MIT License")
|
||||||
|
|
||||||
|
// 2. Parse all TGA sheets
|
||||||
|
println("[2/7] Parsing TGA sprite sheets...")
|
||||||
|
val parser = GlyphSheetParser(assetsDir)
|
||||||
|
val allGlyphs = parser.parseAll()
|
||||||
|
println(" Parsed ${allGlyphs.size} glyphs from sheets")
|
||||||
|
|
||||||
|
// 3. Add all extracted glyphs to BitmapFont
|
||||||
|
println("[3/7] Adding glyphs to BitmapFont...")
|
||||||
|
var addedCount = 0
|
||||||
|
var skippedCount = 0
|
||||||
|
|
||||||
|
for ((codepoint, extracted) in allGlyphs) {
|
||||||
|
// Skip zero-width control characters and surrogates — don't add empty glyphs
|
||||||
|
if (extracted.props.width <= 0 && codepoint != 0x7F) {
|
||||||
|
// Still add zero-width glyphs that have actual bitmap data
|
||||||
|
val hasPixels = extracted.bitmap.any { row -> row.any { it.toInt() and 0xFF != 0 } }
|
||||||
|
if (!hasPixels) {
|
||||||
|
skippedCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip internal-only codepoints that would cause issues
|
||||||
|
if (codepoint in 0x100000..0x10FFFF || codepoint in 0xD800..0xDFFF) {
|
||||||
|
skippedCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val glyph = extractedToBitmapFontGlyph(extracted)
|
||||||
|
font.putCharacter(codepoint, glyph)
|
||||||
|
addedCount++
|
||||||
|
}
|
||||||
|
println(" Added $addedCount glyphs, skipped $skippedCount")
|
||||||
|
|
||||||
|
// 4. Compose Hangul syllables
|
||||||
|
println("[4/7] Composing Hangul syllables...")
|
||||||
|
val hangulCompositor = HangulCompositor(parser)
|
||||||
|
val hangulGlyphs = hangulCompositor.compose()
|
||||||
|
for ((codepoint, pair) in hangulGlyphs) {
|
||||||
|
val (glyph, _) = pair
|
||||||
|
font.putCharacter(codepoint, glyph)
|
||||||
|
}
|
||||||
|
println(" Added ${hangulGlyphs.size} Hangul glyphs")
|
||||||
|
|
||||||
|
// 5. Verify Devanagari/Tamil PUA
|
||||||
|
println("[5/7] Verifying Devanagari/Tamil PUA glyphs...")
|
||||||
|
val devaTamilProcessor = DevanagariTamilProcessor()
|
||||||
|
devaTamilProcessor.verify(allGlyphs)
|
||||||
|
|
||||||
|
// 6. Generate kerning pairs
|
||||||
|
println("[6/7] Generating kerning pairs...")
|
||||||
|
val kemingMachine = KemingMachine()
|
||||||
|
val kernPairs = kemingMachine.generateKerningPairs(allGlyphs)
|
||||||
|
for ((pair, offset) in kernPairs) {
|
||||||
|
font.setKernPair(pair, offset)
|
||||||
|
}
|
||||||
|
println(" Added ${kernPairs.size} kerning pairs")
|
||||||
|
|
||||||
|
// 7. Add spacing characters
|
||||||
|
println("[7/7] Finalising...")
|
||||||
|
addSpacingCharacters(font, allGlyphs)
|
||||||
|
|
||||||
|
// Add .notdef from U+007F (replacement character)
|
||||||
|
allGlyphs[0x7F]?.let {
|
||||||
|
val notdefGlyph = extractedToBitmapFontGlyph(it)
|
||||||
|
font.putNamedGlyph(".notdef", notdefGlyph)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contract glyphs to trim whitespace
|
||||||
|
font.contractGlyphs()
|
||||||
|
|
||||||
|
// Auto-fill any missing name fields
|
||||||
|
font.autoFillNames()
|
||||||
|
|
||||||
|
// Count glyphs
|
||||||
|
val totalGlyphs = font.characters(false).size
|
||||||
|
println()
|
||||||
|
println("Total glyph count: $totalGlyphs")
|
||||||
|
|
||||||
|
// Export
|
||||||
|
println("Exporting to KBITX: $outputPath")
|
||||||
|
val exporter = KbitxBitmapFontExporter()
|
||||||
|
exporter.exportFontToFile(font, File(outputPath))
|
||||||
|
|
||||||
|
println("Done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractedToBitmapFontGlyph(extracted: ExtractedGlyph): BitmapFontGlyph {
|
||||||
|
val bitmap = extracted.bitmap
|
||||||
|
val props = extracted.props
|
||||||
|
val h = bitmap.size
|
||||||
|
val w = if (h > 0) bitmap[0].size else 0
|
||||||
|
|
||||||
|
val glyphData = Array(h) { row ->
|
||||||
|
ByteArray(w) { col -> bitmap[row][col] }
|
||||||
|
}
|
||||||
|
|
||||||
|
val glyph = BitmapFontGlyph()
|
||||||
|
glyph.setGlyph(glyphData)
|
||||||
|
|
||||||
|
// y = distance from top of glyph to baseline
|
||||||
|
// For most glyphs this is 16 (baseline at row 16 from top in a 20px cell)
|
||||||
|
// For Unihan: baseline at row 14 (offset by 2 from the 16px cell centred in 20px)
|
||||||
|
val sheetIndex = getSheetIndex(extracted.codepoint)
|
||||||
|
val baseline = when (sheetIndex) {
|
||||||
|
SheetConfig.SHEET_UNIHAN -> 14
|
||||||
|
SheetConfig.SHEET_CUSTOM_SYM -> 16
|
||||||
|
else -> 16
|
||||||
|
}
|
||||||
|
glyph.setXY(0, baseline)
|
||||||
|
glyph.setCharacterWidth(props.width)
|
||||||
|
|
||||||
|
return glyph
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSheetIndex(codepoint: Int): Int {
|
||||||
|
// Check fixed sheets first
|
||||||
|
if (codepoint in 0xF0000..0xF005F) return SheetConfig.SHEET_BULGARIAN_VARW
|
||||||
|
if (codepoint in 0xF0060..0xF00BF) return SheetConfig.SHEET_SERBIAN_VARW
|
||||||
|
|
||||||
|
for (i in SheetConfig.codeRange.indices.reversed()) {
|
||||||
|
if (codepoint in SheetConfig.codeRange[i]) return i
|
||||||
|
}
|
||||||
|
return SheetConfig.SHEET_UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add spacing characters as empty glyphs with correct advance widths.
|
||||||
|
*/
|
||||||
|
private fun addSpacingCharacters(font: BitmapFont, allGlyphs: Map<Int, ExtractedGlyph>) {
|
||||||
|
val figWidth = allGlyphs[0x30]?.props?.width ?: 9
|
||||||
|
val punctWidth = allGlyphs[0x2E]?.props?.width ?: 6
|
||||||
|
val em = 12 + 1 // as defined in the original
|
||||||
|
|
||||||
|
fun Int.halveWidth() = this / 2 + 1
|
||||||
|
|
||||||
|
val spacings = mapOf(
|
||||||
|
SheetConfig.NQSP to em.halveWidth(),
|
||||||
|
SheetConfig.MQSP to em,
|
||||||
|
SheetConfig.ENSP to em.halveWidth(),
|
||||||
|
SheetConfig.EMSP to em,
|
||||||
|
SheetConfig.THREE_PER_EMSP to (em / 3 + 1),
|
||||||
|
SheetConfig.QUARTER_EMSP to (em / 4 + 1),
|
||||||
|
SheetConfig.SIX_PER_EMSP to (em / 6 + 1),
|
||||||
|
SheetConfig.FSP to figWidth,
|
||||||
|
SheetConfig.PSP to punctWidth,
|
||||||
|
SheetConfig.THSP to 2,
|
||||||
|
SheetConfig.HSP to 1,
|
||||||
|
SheetConfig.ZWSP to 0,
|
||||||
|
SheetConfig.ZWNJ to 0,
|
||||||
|
SheetConfig.ZWJ to 0,
|
||||||
|
SheetConfig.SHY to 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((cp, width) in spacings) {
|
||||||
|
val glyph = BitmapFontGlyph()
|
||||||
|
glyph.setGlyph(Array(SheetConfig.H) { ByteArray(0) })
|
||||||
|
glyph.setXY(0, 16)
|
||||||
|
glyph.setCharacterWidth(width)
|
||||||
|
font.putCharacter(cp, glyph)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NBSP: same width as space
|
||||||
|
val spaceWidth = allGlyphs[32]?.props?.width ?: 7
|
||||||
|
val nbspGlyph = BitmapFontGlyph()
|
||||||
|
nbspGlyph.setGlyph(Array(SheetConfig.H) { ByteArray(0) })
|
||||||
|
nbspGlyph.setXY(0, 16)
|
||||||
|
nbspGlyph.setCharacterWidth(spaceWidth)
|
||||||
|
font.putCharacter(SheetConfig.NBSP, nbspGlyph)
|
||||||
|
}
|
||||||
|
}
|
||||||
120
OTFbuild/src/net/torvald/otfbuild/KemingMachine.kt
Normal file
120
OTFbuild/src/net/torvald/otfbuild/KemingMachine.kt
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package net.torvald.otfbuild
|
||||||
|
|
||||||
|
import com.kreative.bitsnpicas.GlyphPair
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates kerning pairs from shape rules.
|
||||||
|
* Ported from TerrarumSansBitmap.kt "The Keming Machine" section.
|
||||||
|
*/
|
||||||
|
class KemingMachine {
|
||||||
|
|
||||||
|
private class Ing(val s: String) {
|
||||||
|
private var careBits = 0
|
||||||
|
private var ruleBits = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
s.forEachIndexed { index, char ->
|
||||||
|
when (char) {
|
||||||
|
'@' -> {
|
||||||
|
careBits = careBits or SheetConfig.kemingBitMask[index]
|
||||||
|
ruleBits = ruleBits or SheetConfig.kemingBitMask[index]
|
||||||
|
}
|
||||||
|
'`' -> {
|
||||||
|
careBits = careBits or SheetConfig.kemingBitMask[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun matches(shapeBits: Int) = ((shapeBits and careBits) == ruleBits)
|
||||||
|
|
||||||
|
override fun toString() = "C:${careBits.toString(2).padStart(16, '0')}-R:${ruleBits.toString(2).padStart(16, '0')}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Kem(val first: Ing, val second: Ing, val bb: Int = 2, val yy: Int = 1)
|
||||||
|
|
||||||
|
private val kerningRules: List<Kem>
|
||||||
|
|
||||||
|
init {
|
||||||
|
val baseRules = listOf(
|
||||||
|
Kem(Ing("_`_@___`__"), Ing("`_`___@___")),
|
||||||
|
Kem(Ing("_@_`___`__"), Ing("`_________")),
|
||||||
|
Kem(Ing("_@_@___`__"), Ing("`___@_@___"), 1, 1),
|
||||||
|
Kem(Ing("_@_@_`_`__"), Ing("`_____@___")),
|
||||||
|
Kem(Ing("___`_`____"), Ing("`___@_`___")),
|
||||||
|
Kem(Ing("___`_`____"), Ing("`_@___`___")),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Automatically create mirrored versions
|
||||||
|
val mirrored = baseRules.map { rule ->
|
||||||
|
val left = rule.first.s
|
||||||
|
val right = rule.second.s
|
||||||
|
val newLeft = StringBuilder()
|
||||||
|
val newRight = StringBuilder()
|
||||||
|
|
||||||
|
for (c in left.indices step 2) {
|
||||||
|
newLeft.append(right[c + 1]).append(right[c])
|
||||||
|
newRight.append(left[c + 1]).append(left[c])
|
||||||
|
}
|
||||||
|
|
||||||
|
Kem(Ing(newLeft.toString()), Ing(newRight.toString()), rule.bb, rule.yy)
|
||||||
|
}
|
||||||
|
|
||||||
|
kerningRules = baseRules + mirrored
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate kerning pairs from all glyphs that have kerning data.
|
||||||
|
* @return Map of GlyphPair to kern offset (negative values = tighter)
|
||||||
|
*/
|
||||||
|
fun generateKerningPairs(glyphs: Map<Int, ExtractedGlyph>): Map<GlyphPair, Int> {
|
||||||
|
val result = HashMap<GlyphPair, Int>()
|
||||||
|
|
||||||
|
// Collect all codepoints with kerning data
|
||||||
|
val kernableGlyphs = glyphs.filter { it.value.props.hasKernData }
|
||||||
|
|
||||||
|
if (kernableGlyphs.isEmpty()) {
|
||||||
|
println(" [KemingMachine] No glyphs with kern data found")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
println(" [KemingMachine] ${kernableGlyphs.size} glyphs with kern data")
|
||||||
|
|
||||||
|
// Special rule: lowercase r + dot
|
||||||
|
for (r in SheetConfig.lowercaseRs) {
|
||||||
|
for (d in SheetConfig.dots) {
|
||||||
|
if (glyphs.containsKey(r) && glyphs.containsKey(d)) {
|
||||||
|
result[GlyphPair(r, d)] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply kerning rules to all pairs
|
||||||
|
val kernCodes = kernableGlyphs.keys.toIntArray()
|
||||||
|
var pairsFound = 0
|
||||||
|
|
||||||
|
for (leftCode in kernCodes) {
|
||||||
|
val leftProps = kernableGlyphs[leftCode]!!.props
|
||||||
|
val maskL = leftProps.kerningMask
|
||||||
|
|
||||||
|
for (rightCode in kernCodes) {
|
||||||
|
val rightProps = kernableGlyphs[rightCode]!!.props
|
||||||
|
val maskR = rightProps.kerningMask
|
||||||
|
|
||||||
|
for (rule in kerningRules) {
|
||||||
|
if (rule.first.matches(maskL) && rule.second.matches(maskR)) {
|
||||||
|
val contraction = if (leftProps.isKernYtype || rightProps.isKernYtype) rule.yy else rule.bb
|
||||||
|
if (contraction > 0) {
|
||||||
|
result[GlyphPair(leftCode, rightCode)] = -contraction
|
||||||
|
pairsFound++
|
||||||
|
}
|
||||||
|
break // first matching rule wins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println(" [KemingMachine] Generated $pairsFound kerning pairs (+ ${SheetConfig.lowercaseRs.size * SheetConfig.dots.size} r-dot pairs)")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
7
OTFbuild/src/net/torvald/otfbuild/Main.kt
Normal file
7
OTFbuild/src/net/torvald/otfbuild/Main.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package net.torvald.otfbuild
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val assetsDir = args.getOrElse(0) { "src/assets" }
|
||||||
|
val outputPath = args.getOrElse(1) { "OTFbuild/TerrarumSansBitmap.kbitx" }
|
||||||
|
KbitxBuilder(assetsDir).build(outputPath)
|
||||||
|
}
|
||||||
377
OTFbuild/src/net/torvald/otfbuild/SheetConfig.kt
Normal file
377
OTFbuild/src/net/torvald/otfbuild/SheetConfig.kt
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
package net.torvald.otfbuild
|
||||||
|
|
||||||
|
typealias CodePoint = Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ported from TerrarumSansBitmap.kt companion object.
|
||||||
|
* All sheet definitions, code ranges, index functions, and font metric constants.
|
||||||
|
*/
|
||||||
|
object SheetConfig {
|
||||||
|
|
||||||
|
// Font metrics
|
||||||
|
const val H = 20
|
||||||
|
const val H_UNIHAN = 16
|
||||||
|
const val W_HANGUL_BASE = 13
|
||||||
|
const val W_UNIHAN = 16
|
||||||
|
const val W_LATIN_WIDE = 9
|
||||||
|
const val W_VAR_INIT = 15
|
||||||
|
const val W_WIDEVAR_INIT = 31
|
||||||
|
const val HGAP_VAR = 1
|
||||||
|
const val SIZE_CUSTOM_SYM = 20
|
||||||
|
|
||||||
|
const val H_DIACRITICS = 3
|
||||||
|
const val H_STACKUP_LOWERCASE_SHIFTDOWN = 4
|
||||||
|
const val H_OVERLAY_LOWERCASE_SHIFTDOWN = 2
|
||||||
|
|
||||||
|
const val LINE_HEIGHT = 24
|
||||||
|
|
||||||
|
// Sheet indices
|
||||||
|
const val SHEET_ASCII_VARW = 0
|
||||||
|
const val SHEET_HANGUL = 1
|
||||||
|
const val SHEET_EXTA_VARW = 2
|
||||||
|
const val SHEET_EXTB_VARW = 3
|
||||||
|
const val SHEET_KANA = 4
|
||||||
|
const val SHEET_CJK_PUNCT = 5
|
||||||
|
const val SHEET_UNIHAN = 6
|
||||||
|
const val SHEET_CYRILIC_VARW = 7
|
||||||
|
const val SHEET_HALFWIDTH_FULLWIDTH_VARW = 8
|
||||||
|
const val SHEET_UNI_PUNCT_VARW = 9
|
||||||
|
const val SHEET_GREEK_VARW = 10
|
||||||
|
const val SHEET_THAI_VARW = 11
|
||||||
|
const val SHEET_HAYEREN_VARW = 12
|
||||||
|
const val SHEET_KARTULI_VARW = 13
|
||||||
|
const val SHEET_IPA_VARW = 14
|
||||||
|
const val SHEET_RUNIC = 15
|
||||||
|
const val SHEET_LATIN_EXT_ADD_VARW = 16
|
||||||
|
const val SHEET_CUSTOM_SYM = 17
|
||||||
|
const val SHEET_BULGARIAN_VARW = 18
|
||||||
|
const val SHEET_SERBIAN_VARW = 19
|
||||||
|
const val SHEET_TSALAGI_VARW = 20
|
||||||
|
const val SHEET_PHONETIC_EXT_VARW = 21
|
||||||
|
const val SHEET_DEVANAGARI_VARW = 22
|
||||||
|
const val SHEET_KARTULI_CAPS_VARW = 23
|
||||||
|
const val SHEET_DIACRITICAL_MARKS_VARW = 24
|
||||||
|
const val SHEET_GREEK_POLY_VARW = 25
|
||||||
|
const val SHEET_EXTC_VARW = 26
|
||||||
|
const val SHEET_EXTD_VARW = 27
|
||||||
|
const val SHEET_CURRENCIES_VARW = 28
|
||||||
|
const val SHEET_INTERNAL_VARW = 29
|
||||||
|
const val SHEET_LETTERLIKE_MATHS_VARW = 30
|
||||||
|
const val SHEET_ENCLOSED_ALPHNUM_SUPL_VARW = 31
|
||||||
|
const val SHEET_TAMIL_VARW = 32
|
||||||
|
const val SHEET_BENGALI_VARW = 33
|
||||||
|
const val SHEET_BRAILLE_VARW = 34
|
||||||
|
const val SHEET_SUNDANESE_VARW = 35
|
||||||
|
const val SHEET_DEVANAGARI2_INTERNAL_VARW = 36
|
||||||
|
const val SHEET_CODESTYLE_ASCII_VARW = 37
|
||||||
|
const val SHEET_ALPHABETIC_PRESENTATION_FORMS = 38
|
||||||
|
const val SHEET_HENTAIGANA_VARW = 39
|
||||||
|
|
||||||
|
const val SHEET_UNKNOWN = 254
|
||||||
|
|
||||||
|
val fileList = arrayOf(
|
||||||
|
"ascii_variable.tga",
|
||||||
|
"hangul_johab.tga",
|
||||||
|
"latinExtA_variable.tga",
|
||||||
|
"latinExtB_variable.tga",
|
||||||
|
"kana_variable.tga",
|
||||||
|
"cjkpunct_variable.tga",
|
||||||
|
"wenquanyi.tga",
|
||||||
|
"cyrilic_variable.tga",
|
||||||
|
"halfwidth_fullwidth_variable.tga",
|
||||||
|
"unipunct_variable.tga",
|
||||||
|
"greek_variable.tga",
|
||||||
|
"thai_variable.tga",
|
||||||
|
"hayeren_variable.tga",
|
||||||
|
"kartuli_variable.tga",
|
||||||
|
"ipa_ext_variable.tga",
|
||||||
|
"futhark.tga",
|
||||||
|
"latinExt_additional_variable.tga",
|
||||||
|
"puae000-e0ff.tga",
|
||||||
|
"cyrilic_bulgarian_variable.tga",
|
||||||
|
"cyrilic_serbian_variable.tga",
|
||||||
|
"tsalagi_variable.tga",
|
||||||
|
"phonetic_extensions_variable.tga",
|
||||||
|
"devanagari_variable.tga",
|
||||||
|
"kartuli_allcaps_variable.tga",
|
||||||
|
"diacritical_marks_variable.tga",
|
||||||
|
"greek_polytonic_xyswap_variable.tga",
|
||||||
|
"latinExtC_variable.tga",
|
||||||
|
"latinExtD_variable.tga",
|
||||||
|
"currencies_variable.tga",
|
||||||
|
"internal_variable.tga",
|
||||||
|
"letterlike_symbols_variable.tga",
|
||||||
|
"enclosed_alphanumeric_supplement_variable.tga",
|
||||||
|
"tamil_extrawide_variable.tga",
|
||||||
|
"bengali_variable.tga",
|
||||||
|
"braille_variable.tga",
|
||||||
|
"sundanese_variable.tga",
|
||||||
|
"devanagari_internal_extrawide_variable.tga",
|
||||||
|
"pua_codestyle_ascii_variable.tga",
|
||||||
|
"alphabetic_presentation_forms_extrawide_variable.tga",
|
||||||
|
"hentaigana_variable.tga",
|
||||||
|
)
|
||||||
|
|
||||||
|
val codeRange: Array<List<Int>> = arrayOf(
|
||||||
|
(0..0xFF).toList(),
|
||||||
|
(0x1100..0x11FF).toList() + (0xA960..0xA97F).toList() + (0xD7B0..0xD7FF).toList(),
|
||||||
|
(0x100..0x17F).toList(),
|
||||||
|
(0x180..0x24F).toList(),
|
||||||
|
(0x3040..0x30FF).toList() + (0x31F0..0x31FF).toList(),
|
||||||
|
(0x3000..0x303F).toList(),
|
||||||
|
(0x3400..0x9FFF).toList(),
|
||||||
|
(0x400..0x52F).toList(),
|
||||||
|
(0xFF00..0xFFFF).toList(),
|
||||||
|
(0x2000..0x209F).toList(),
|
||||||
|
(0x370..0x3CE).toList(),
|
||||||
|
(0xE00..0xE5F).toList(),
|
||||||
|
(0x530..0x58F).toList(),
|
||||||
|
(0x10D0..0x10FF).toList(),
|
||||||
|
(0x250..0x2FF).toList(),
|
||||||
|
(0x16A0..0x16FF).toList(),
|
||||||
|
(0x1E00..0x1EFF).toList(),
|
||||||
|
(0xE000..0xE0FF).toList(),
|
||||||
|
(0xF0000..0xF005F).toList(),
|
||||||
|
(0xF0060..0xF00BF).toList(),
|
||||||
|
(0x13A0..0x13F5).toList(),
|
||||||
|
(0x1D00..0x1DBF).toList(),
|
||||||
|
(0x900..0x97F).toList() + (0xF0100..0xF04FF).toList(),
|
||||||
|
(0x1C90..0x1CBF).toList(),
|
||||||
|
(0x300..0x36F).toList(),
|
||||||
|
(0x1F00..0x1FFF).toList(),
|
||||||
|
(0x2C60..0x2C7F).toList(),
|
||||||
|
(0xA720..0xA7FF).toList(),
|
||||||
|
(0x20A0..0x20CF).toList(),
|
||||||
|
(0xFFE00..0xFFF9F).toList(),
|
||||||
|
(0x2100..0x214F).toList(),
|
||||||
|
(0x1F100..0x1F1FF).toList(),
|
||||||
|
(0x0B80..0x0BFF).toList() + (0xF00C0..0xF00FF).toList(),
|
||||||
|
(0x980..0x9FF).toList(),
|
||||||
|
(0x2800..0x28FF).toList(),
|
||||||
|
(0x1B80..0x1BBF).toList() + (0x1CC0..0x1CCF).toList() + (0xF0500..0xF050F).toList(),
|
||||||
|
(0xF0110..0xF012F).toList(),
|
||||||
|
(0xF0520..0xF057F).toList(),
|
||||||
|
(0xFB00..0xFB17).toList(),
|
||||||
|
(0x1B000..0x1B16F).toList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val codeRangeHangulCompat = 0x3130..0x318F
|
||||||
|
|
||||||
|
val altCharsetCodepointOffsets = intArrayOf(
|
||||||
|
0,
|
||||||
|
0xF0000 - 0x400, // Bulgarian
|
||||||
|
0xF0060 - 0x400, // Serbian
|
||||||
|
0xF0520 - 0x20, // Codestyle
|
||||||
|
)
|
||||||
|
|
||||||
|
val altCharsetCodepointDomains = arrayOf(
|
||||||
|
0..0x10FFFF,
|
||||||
|
0x400..0x45F,
|
||||||
|
0x400..0x45F,
|
||||||
|
0x20..0x7F,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unicode spacing characters
|
||||||
|
const val NQSP = 0x2000
|
||||||
|
const val MQSP = 0x2001
|
||||||
|
const val ENSP = 0x2002
|
||||||
|
const val EMSP = 0x2003
|
||||||
|
const val THREE_PER_EMSP = 0x2004
|
||||||
|
const val QUARTER_EMSP = 0x2005
|
||||||
|
const val SIX_PER_EMSP = 0x2006
|
||||||
|
const val FSP = 0x2007
|
||||||
|
const val PSP = 0x2008
|
||||||
|
const val THSP = 0x2009
|
||||||
|
const val HSP = 0x200A
|
||||||
|
const val ZWSP = 0x200B
|
||||||
|
const val ZWNJ = 0x200C
|
||||||
|
const val ZWJ = 0x200D
|
||||||
|
const val SHY = 0xAD
|
||||||
|
const val NBSP = 0xA0
|
||||||
|
const val OBJ = 0xFFFC
|
||||||
|
|
||||||
|
const val FIXED_BLOCK_1 = 0xFFFD0
|
||||||
|
const val MOVABLE_BLOCK_M1 = 0xFFFE0
|
||||||
|
const val MOVABLE_BLOCK_1 = 0xFFFF0
|
||||||
|
|
||||||
|
const val CHARSET_OVERRIDE_DEFAULT = 0xFFFC0
|
||||||
|
const val CHARSET_OVERRIDE_BG_BG = 0xFFFC1
|
||||||
|
const val CHARSET_OVERRIDE_SR_SR = 0xFFFC2
|
||||||
|
const val CHARSET_OVERRIDE_CODESTYLE = 0xFFFC3
|
||||||
|
|
||||||
|
// Sheet type detection
|
||||||
|
fun isVariable(filename: String) = filename.endsWith("_variable.tga")
|
||||||
|
fun isXYSwapped(filename: String) = filename.contains("xyswap", ignoreCase = true)
|
||||||
|
fun isExtraWide(filename: String) = filename.contains("extrawide", ignoreCase = true)
|
||||||
|
|
||||||
|
/** Returns the cell width for a given sheet index. */
|
||||||
|
fun getCellWidth(sheetIndex: Int): Int = when {
|
||||||
|
isExtraWide(fileList[sheetIndex]) -> W_WIDEVAR_INIT
|
||||||
|
isVariable(fileList[sheetIndex]) -> W_VAR_INIT
|
||||||
|
sheetIndex == SHEET_UNIHAN -> W_UNIHAN
|
||||||
|
sheetIndex == SHEET_HANGUL -> W_HANGUL_BASE
|
||||||
|
sheetIndex == SHEET_CUSTOM_SYM -> SIZE_CUSTOM_SYM
|
||||||
|
sheetIndex == SHEET_RUNIC -> W_LATIN_WIDE
|
||||||
|
else -> W_VAR_INIT
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the cell height for a given sheet index. */
|
||||||
|
fun getCellHeight(sheetIndex: Int): Int = when (sheetIndex) {
|
||||||
|
SHEET_UNIHAN -> H_UNIHAN
|
||||||
|
SHEET_CUSTOM_SYM -> SIZE_CUSTOM_SYM
|
||||||
|
else -> H
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Number of columns per row for the sheet. */
|
||||||
|
fun getColumns(sheetIndex: Int): Int = when (sheetIndex) {
|
||||||
|
SHEET_UNIHAN -> 256
|
||||||
|
else -> 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index functions (X position in sheet)
|
||||||
|
fun indexX(c: CodePoint): Int = c % 16
|
||||||
|
fun unihanIndexX(c: CodePoint): Int = (c - 0x3400) % 256
|
||||||
|
|
||||||
|
// Index functions (Y position in sheet) — per sheet type
|
||||||
|
fun indexY(sheetIndex: Int, c: CodePoint): Int = when (sheetIndex) {
|
||||||
|
SHEET_ASCII_VARW -> c / 16
|
||||||
|
SHEET_UNIHAN -> unihanIndexY(c)
|
||||||
|
SHEET_EXTA_VARW -> (c - 0x100) / 16
|
||||||
|
SHEET_EXTB_VARW -> (c - 0x180) / 16
|
||||||
|
SHEET_KANA -> kanaIndexY(c)
|
||||||
|
SHEET_CJK_PUNCT -> (c - 0x3000) / 16
|
||||||
|
SHEET_CYRILIC_VARW -> (c - 0x400) / 16
|
||||||
|
SHEET_HALFWIDTH_FULLWIDTH_VARW -> (c - 0xFF00) / 16
|
||||||
|
SHEET_UNI_PUNCT_VARW -> (c - 0x2000) / 16
|
||||||
|
SHEET_GREEK_VARW -> (c - 0x370) / 16
|
||||||
|
SHEET_THAI_VARW -> (c - 0xE00) / 16
|
||||||
|
SHEET_CUSTOM_SYM -> (c - 0xE000) / 16
|
||||||
|
SHEET_HAYEREN_VARW -> (c - 0x530) / 16
|
||||||
|
SHEET_KARTULI_VARW -> (c - 0x10D0) / 16
|
||||||
|
SHEET_IPA_VARW -> (c - 0x250) / 16
|
||||||
|
SHEET_RUNIC -> (c - 0x16A0) / 16
|
||||||
|
SHEET_LATIN_EXT_ADD_VARW -> (c - 0x1E00) / 16
|
||||||
|
SHEET_BULGARIAN_VARW -> (c - 0xF0000) / 16
|
||||||
|
SHEET_SERBIAN_VARW -> (c - 0xF0060) / 16
|
||||||
|
SHEET_TSALAGI_VARW -> (c - 0x13A0) / 16
|
||||||
|
SHEET_PHONETIC_EXT_VARW -> (c - 0x1D00) / 16
|
||||||
|
SHEET_DEVANAGARI_VARW -> devanagariIndexY(c)
|
||||||
|
SHEET_KARTULI_CAPS_VARW -> (c - 0x1C90) / 16
|
||||||
|
SHEET_DIACRITICAL_MARKS_VARW -> (c - 0x300) / 16
|
||||||
|
SHEET_GREEK_POLY_VARW -> (c - 0x1F00) / 16
|
||||||
|
SHEET_EXTC_VARW -> (c - 0x2C60) / 16
|
||||||
|
SHEET_EXTD_VARW -> (c - 0xA720) / 16
|
||||||
|
SHEET_CURRENCIES_VARW -> (c - 0x20A0) / 16
|
||||||
|
SHEET_INTERNAL_VARW -> (c - 0xFFE00) / 16
|
||||||
|
SHEET_LETTERLIKE_MATHS_VARW -> (c - 0x2100) / 16
|
||||||
|
SHEET_ENCLOSED_ALPHNUM_SUPL_VARW -> (c - 0x1F100) / 16
|
||||||
|
SHEET_TAMIL_VARW -> tamilIndexY(c)
|
||||||
|
SHEET_BENGALI_VARW -> (c - 0x980) / 16
|
||||||
|
SHEET_BRAILLE_VARW -> (c - 0x2800) / 16
|
||||||
|
SHEET_SUNDANESE_VARW -> sundaneseIndexY(c)
|
||||||
|
SHEET_DEVANAGARI2_INTERNAL_VARW -> (c - 0xF0110) / 16
|
||||||
|
SHEET_CODESTYLE_ASCII_VARW -> (c - 0xF0520) / 16
|
||||||
|
SHEET_ALPHABETIC_PRESENTATION_FORMS -> (c - 0xFB00) / 16
|
||||||
|
SHEET_HENTAIGANA_VARW -> (c - 0x1B000) / 16
|
||||||
|
SHEET_HANGUL -> 0 // Hangul uses special row logic
|
||||||
|
else -> c / 16
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun kanaIndexY(c: CodePoint): Int =
|
||||||
|
if (c in 0x31F0..0x31FF) 12
|
||||||
|
else (c - 0x3040) / 16
|
||||||
|
|
||||||
|
private fun unihanIndexY(c: CodePoint): Int = (c - 0x3400) / 256
|
||||||
|
|
||||||
|
private fun devanagariIndexY(c: CodePoint): Int =
|
||||||
|
(if (c < 0xF0000) (c - 0x0900) else (c - 0xF0080)) / 16
|
||||||
|
|
||||||
|
private fun tamilIndexY(c: CodePoint): Int =
|
||||||
|
(if (c < 0xF0000) (c - 0x0B80) else (c - 0xF0040)) / 16
|
||||||
|
|
||||||
|
private fun sundaneseIndexY(c: CodePoint): Int =
|
||||||
|
(if (c >= 0xF0500) (c - 0xF04B0) else if (c < 0x1BC0) (c - 0x1B80) else (c - 0x1C80)) / 16
|
||||||
|
|
||||||
|
// Hangul constants
|
||||||
|
const val JUNG_COUNT = 21
|
||||||
|
const val JONG_COUNT = 28
|
||||||
|
|
||||||
|
// Hangul shape arrays (sorted)
|
||||||
|
val jungseongI = sortedSetOf(21, 61)
|
||||||
|
val jungseongOU = sortedSetOf(9, 13, 14, 18, 34, 35, 39, 45, 51, 53, 54, 64, 73, 80, 83)
|
||||||
|
val jungseongOUComplex = (listOf(10, 11, 16) + (22..33).toList() + listOf(36, 37, 38) + (41..44).toList() +
|
||||||
|
(46..50).toList() + (56..59).toList() + listOf(63) + (67..72).toList() + (74..79).toList() +
|
||||||
|
(81..83).toList() + (85..91).toList() + listOf(93, 94)).toSortedSet()
|
||||||
|
val jungseongRightie = sortedSetOf(2, 4, 6, 8, 11, 16, 32, 33, 37, 42, 44, 48, 50, 71, 72, 75, 78, 79, 83, 86, 87, 88, 94)
|
||||||
|
val jungseongOEWI = sortedSetOf(12, 15, 17, 40, 52, 55, 89, 90, 91)
|
||||||
|
val jungseongEU = sortedSetOf(19, 62, 66)
|
||||||
|
val jungseongYI = sortedSetOf(20, 60, 65)
|
||||||
|
val jungseongUU = sortedSetOf(14, 15, 16, 17, 18, 27, 30, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 59, 67, 68, 73, 77, 78, 79, 80, 81, 82, 83, 84, 91)
|
||||||
|
val jungseongWide = (jungseongOU.toList() + jungseongEU.toList()).toSortedSet()
|
||||||
|
val choseongGiyeoks = sortedSetOf(0, 1, 15, 23, 30, 34, 45, 51, 56, 65, 82, 90, 100, 101, 110, 111, 115)
|
||||||
|
val hangulPeaksWithExtraWidth = sortedSetOf(2, 4, 6, 8, 11, 16, 32, 33, 37, 42, 44, 48, 50, 71, 75, 78, 79, 83, 86, 87, 88, 94)
|
||||||
|
|
||||||
|
val giyeokRemapping = hashMapOf(
|
||||||
|
5 to 19, 6 to 20, 7 to 21, 8 to 22, 11 to 23, 12 to 24,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun isHangulChoseong(c: CodePoint) = c in 0x1100..0x115F || c in 0xA960..0xA97F
|
||||||
|
fun isHangulJungseong(c: CodePoint) = c in 0x1160..0x11A7 || c in 0xD7B0..0xD7C6
|
||||||
|
fun isHangulJongseong(c: CodePoint) = c in 0x11A8..0x11FF || c in 0xD7CB..0xD7FB
|
||||||
|
fun isHangulCompat(c: CodePoint) = c in codeRangeHangulCompat
|
||||||
|
|
||||||
|
fun toHangulChoseongIndex(c: CodePoint): Int =
|
||||||
|
if (c in 0x1100..0x115F) c - 0x1100
|
||||||
|
else if (c in 0xA960..0xA97F) c - 0xA960 + 96
|
||||||
|
else throw IllegalArgumentException("Not a choseong: U+${c.toString(16)}")
|
||||||
|
|
||||||
|
fun toHangulJungseongIndex(c: CodePoint): Int? =
|
||||||
|
if (c in 0x1160..0x11A7) c - 0x1160
|
||||||
|
else if (c in 0xD7B0..0xD7C6) c - 0xD7B0 + 72
|
||||||
|
else null
|
||||||
|
|
||||||
|
fun toHangulJongseongIndex(c: CodePoint): Int? =
|
||||||
|
if (c in 0x11A8..0x11FF) c - 0x11A8 + 1
|
||||||
|
else if (c in 0xD7CB..0xD7FB) c - 0xD7CB + 88 + 1
|
||||||
|
else null
|
||||||
|
|
||||||
|
fun getHanInitialRow(i: Int, p: Int, f: Int): Int {
|
||||||
|
var ret = when {
|
||||||
|
p in jungseongI -> 3
|
||||||
|
p in jungseongOEWI -> 11
|
||||||
|
p in jungseongOUComplex -> 7
|
||||||
|
p in jungseongOU -> 5
|
||||||
|
p in jungseongEU -> 9
|
||||||
|
p in jungseongYI -> 13
|
||||||
|
else -> 1
|
||||||
|
}
|
||||||
|
if (f != 0) ret += 1
|
||||||
|
return if (p in jungseongUU && i in choseongGiyeoks) {
|
||||||
|
giyeokRemapping[ret] ?: throw NullPointerException("i=$i p=$p f=$f ret=$ret")
|
||||||
|
} else ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHanMedialRow(i: Int, p: Int, f: Int): Int = if (f == 0) 15 else 16
|
||||||
|
|
||||||
|
fun getHanFinalRow(i: Int, p: Int, f: Int): Int =
|
||||||
|
if (p !in jungseongRightie) 17 else 18
|
||||||
|
|
||||||
|
// Kerning constants
|
||||||
|
val kemingBitMask: IntArray = intArrayOf(7, 6, 5, 4, 3, 2, 1, 0, 15, 14).map { 1 shl it }.toIntArray()
|
||||||
|
|
||||||
|
// Special characters for r+dot kerning
|
||||||
|
val lowercaseRs = sortedSetOf(0x72, 0x155, 0x157, 0x159, 0x211, 0x213, 0x27c, 0x1e59, 0x1e58, 0x1e5f)
|
||||||
|
val dots = sortedSetOf(0x2c, 0x2e)
|
||||||
|
|
||||||
|
// Devanagari internal encoding
|
||||||
|
fun Int.toDevaInternal(): Int {
|
||||||
|
if (this in 0x0915..0x0939) return this - 0x0915 + 0xF0140
|
||||||
|
else if (this in 0x0958..0x095F) return devanagariUnicodeNuqtaTable[this - 0x0958]
|
||||||
|
else throw IllegalArgumentException("No internal form for U+${this.toString(16)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val devanagariUnicodeNuqtaTable = intArrayOf(0xF0170, 0xF0171, 0xF0172, 0xF0177, 0xF017C, 0xF017D, 0xF0186, 0xF018A)
|
||||||
|
|
||||||
|
val devanagariConsonants = ((0x0915..0x0939).toList() + (0x0958..0x095F).toList() + (0x0978..0x097F).toList() +
|
||||||
|
(0xF0140..0xF04FF).toList() + (0xF0106..0xF0109).toList()).toHashSet()
|
||||||
|
}
|
||||||
80
OTFbuild/src/net/torvald/otfbuild/TgaReader.kt
Normal file
80
OTFbuild/src/net/torvald/otfbuild/TgaReader.kt
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package net.torvald.otfbuild
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple TGA reader for uncompressed true-colour images (Type 2).
|
||||||
|
* Returns RGBA8888 pixel data.
|
||||||
|
*/
|
||||||
|
class TgaImage(val width: Int, val height: Int, val pixels: IntArray) {
|
||||||
|
/** Get pixel at (x, y) as RGBA8888. */
|
||||||
|
fun getPixel(x: Int, y: Int): Int {
|
||||||
|
if (x < 0 || x >= width || y < 0 || y >= height) return 0
|
||||||
|
return pixels[y * width + x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object TgaReader {
|
||||||
|
|
||||||
|
fun read(file: File): TgaImage = read(file.inputStream())
|
||||||
|
|
||||||
|
fun read(input: InputStream): TgaImage {
|
||||||
|
val data = input.use { it.readBytes() }
|
||||||
|
var pos = 0
|
||||||
|
|
||||||
|
fun u8() = data[pos++].toInt() and 0xFF
|
||||||
|
fun u16() = u8() or (u8() shl 8)
|
||||||
|
|
||||||
|
val idLength = u8()
|
||||||
|
val colourMapType = u8()
|
||||||
|
val imageType = u8()
|
||||||
|
|
||||||
|
// colour map spec (5 bytes)
|
||||||
|
u16(); u16(); u8()
|
||||||
|
|
||||||
|
// image spec
|
||||||
|
val xOrigin = u16()
|
||||||
|
val yOrigin = u16()
|
||||||
|
val width = u16()
|
||||||
|
val height = u16()
|
||||||
|
val bitsPerPixel = u8()
|
||||||
|
val descriptor = u8()
|
||||||
|
|
||||||
|
val topToBottom = (descriptor and 0x20) != 0
|
||||||
|
val bytesPerPixel = bitsPerPixel / 8
|
||||||
|
|
||||||
|
// skip ID
|
||||||
|
pos += idLength
|
||||||
|
|
||||||
|
// skip colour map
|
||||||
|
if (colourMapType != 0) {
|
||||||
|
throw UnsupportedOperationException("Colour-mapped TGA not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageType != 2) {
|
||||||
|
throw UnsupportedOperationException("Only uncompressed true-colour TGA is supported (type 2), got type $imageType")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesPerPixel !in 3..4) {
|
||||||
|
throw UnsupportedOperationException("Only 24-bit or 32-bit TGA supported, got ${bitsPerPixel}-bit")
|
||||||
|
}
|
||||||
|
|
||||||
|
val pixels = IntArray(width * height)
|
||||||
|
|
||||||
|
for (row in 0 until height) {
|
||||||
|
val y = if (topToBottom) row else (height - 1 - row)
|
||||||
|
for (x in 0 until width) {
|
||||||
|
val b = data[pos++].toInt() and 0xFF
|
||||||
|
val g = data[pos++].toInt() and 0xFF
|
||||||
|
val r = data[pos++].toInt() and 0xFF
|
||||||
|
val a = if (bytesPerPixel == 4) data[pos++].toInt() and 0xFF else 0xFF
|
||||||
|
|
||||||
|
// Store as RGBA8888
|
||||||
|
pixels[y * width + x] = (r shl 24) or (g shl 16) or (b shl 8) or a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TgaImage(width, height, pixels)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3022,7 +3022,7 @@ class TerrarumSansBitmap(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// The "Keming" Machine //
|
// The Keming Machine //
|
||||||
|
|
||||||
private val kemingBitMask: IntArray = intArrayOf(7,6,5,4,3,2,1,0,15,14).map { 1 shl it }.toIntArray()
|
private val kemingBitMask: IntArray = intArrayOf(7,6,5,4,3,2,1,0,15,14).map { 1 shl it }.toIntArray()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user