Skip to content

Commit 514ec01

Browse files
committed
feat: editor for conversion maps
1 parent 6cb3628 commit 514ec01

2 files changed

Lines changed: 220 additions & 0 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Unicode Conversion Maps
2+
3+
This is a global repository to hold community curated maps to convert from ASCII to Unicode.
4+
5+
## History
6+
7+
* [Payyans](https://github.com/libindic/payyans) is the origin of a lot of map files.
8+
* [Freaknz](https://gitlab.com/kannanvm/freaknz-qt/) brought renewed interest in ASCII -> Unicode conversion
9+
* [ttf.js](https://github.com/ynakajima/ttf.js) is where the idea of showing glyphs in the browser came from.
10+
* [opentype.js](https://github.com/opentypejs/opentype.js) allowed for bug-free opening of glyphs.

editor/index.html

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>ASCII Unicode Conversion Maps Creator</title>
6+
<script type="importmap">
7+
{
8+
"imports": {
9+
"vue": "http://unpkg.com/vue@3/dist/vue.esm-browser.js",
10+
"opentype": "http://unpkg.com/opentype.js/dist/opentype.module.js"
11+
}
12+
}
13+
</script>
14+
<style>
15+
header {
16+
display: flex;
17+
flex-direction: row;
18+
flex-wrap: wrap;
19+
padding: 4em;
20+
}
21+
22+
#glyfs {
23+
display: flex;
24+
flex-direction: row;
25+
flex-wrap: wrap;
26+
gap: 1em;
27+
}
28+
29+
.glyf {
30+
border: 1px solid black;
31+
min-width: 40px;
32+
}
33+
34+
canvas {
35+
width: 8em;
36+
height: 4em;
37+
}
38+
39+
.glyfHeader {
40+
background-color: blue;
41+
color: white;
42+
}
43+
44+
.glyfContent {
45+
display: flex;
46+
flex-direction: row;
47+
}
48+
49+
.glyfContentRight {
50+
display: flex;
51+
flex-direction: column;
52+
}
53+
54+
.serial {
55+
background-color: lightgreen;
56+
color: black;
57+
}
58+
59+
.mapinput {
60+
height: 1em;
61+
font-size: 50px;
62+
}
63+
</style>
64+
</head>
65+
66+
<body>
67+
<div id="app">
68+
<h1>ASCII Unicode Conversion Map Editor</h1>
69+
<header>
70+
<div>
71+
<label for="read">Choose Font</label>
72+
<input type="file" @change="onChangeFont" id="read" />
73+
</div>
74+
<div>
75+
<label for="read">Choose Map</label><input type="file" @change="onChangeMap" id="map" />
76+
</div>
77+
<div>
78+
<textarea id="mapcontents" v-model="mapValue"></textarea>
79+
<button @click="copyMap">Copy Map</button>
80+
</div>
81+
</header>
82+
<div id="glyfs">
83+
<div class="glyf" v-for="(char, index) in asciiChars" :key="char">
84+
<div class="glyfHeader"><span class="serial">{{ ASCII_START + index }}</span> {{ char }}</div>
85+
<div class="glyfContent">
86+
<canvas :ref="(el) => storeGlyfCanvas(char, el)"></canvas>
87+
<div class="glyfContentRight">
88+
<input v-model="mappings[char]" size="1" class="mapinput"
89+
@change="e => changeMapping(char, e.target.value)" />
90+
<span>{{ getCharCode(mappings[char]) }}</span>
91+
</div>
92+
</div>
93+
</div>
94+
</div>
95+
</div>
96+
97+
<script type="module">
98+
import { createApp, reactive, ref } from 'vue'
99+
import { parse } from 'opentype'
100+
const ASCII_START = 33;
101+
const ASCII_END = 256;
102+
createApp({
103+
setup() {
104+
const asciiChars = [];
105+
for (let i = ASCII_START; i < ASCII_END; i++) {
106+
asciiChars.push(String.fromCharCode(i));
107+
}
108+
const glyfCanvases = reactive({})
109+
const storeGlyfCanvas = (char, el) => glyfCanvases[char] = el;
110+
const mapValue = ref("");
111+
const mappings = reactive({});
112+
return {
113+
asciiChars,
114+
glyfCanvases,
115+
storeGlyfCanvas,
116+
ASCII_START,
117+
mapValue,
118+
mappings,
119+
}
120+
},
121+
methods: {
122+
async copyMap() {
123+
try {
124+
await navigator.clipboard.writeText(this.mapValue);
125+
alert('Copied map to clipboard. Consider contributing to community at https://github.com/libindic/unicode-conversion-maps');
126+
} catch ($e) {
127+
alert('Cannot copy. Manually copy from text area ');
128+
}
129+
},
130+
changeMapping(char, value) {
131+
this.mapValue = this.mapValue
132+
.split('\n')
133+
.map(line => {
134+
if (line.startsWith('#')) return line;
135+
let [lhs, rhs] = line.split('=').map(w => w.trim())
136+
if (lhs === char) {
137+
return `${lhs}=${value}`
138+
} else {
139+
return line
140+
}
141+
})
142+
.join('\n')
143+
},
144+
getCharCode(text) {
145+
if (!text) return "";
146+
return text.split('')
147+
.map(k =>
148+
`U${k
149+
.charCodeAt(0)
150+
.toString(16)
151+
.toUpperCase()
152+
.padStart(4, '0')
153+
}`)
154+
.join('+')
155+
},
156+
readMapFile(text) {
157+
const lines = text.split('\n');
158+
lines.forEach(line => {
159+
if (line.startsWith('#')) return;
160+
let [lhs, rhs] = line.split('=').map(w => w.trim())
161+
this.mappings[lhs] = rhs;
162+
})
163+
},
164+
onChangeMap(e) {
165+
const file = e.target.files[0];
166+
const reader = new FileReader();
167+
reader.onload = (e) => {
168+
this.mapValue = e.target.result;
169+
this.readMapFile(e.target.result);
170+
}
171+
reader.onerror = (e) => {
172+
console.log("err", e);
173+
};
174+
reader.readAsText(file);
175+
},
176+
renderGlyph(char, glyph) {
177+
const canvas = this.glyfCanvases[char]
178+
glyph.draw(canvas.getContext("2d"), 50, 75, 100);
179+
},
180+
onChangeFont(e) {
181+
const file = e.target.files[0];
182+
const reader = new FileReader();
183+
reader.onload = (e) => {
184+
const font = parse(e.target.result);
185+
const scale = 1000 / font.unitsPerEm;
186+
const glyphDict = {}
187+
Object.entries(font.glyphs.glyphs).forEach(([index, glyph]) => {
188+
glyphDict[glyph.unicode] = glyph;
189+
});
190+
console.log(this.asciiChars);
191+
for (let i = ASCII_START; i < ASCII_END; i++) {
192+
193+
const char = this.asciiChars[i - ASCII_START];
194+
if (i in glyphDict) {
195+
this.renderGlyph(char, glyphDict[i])
196+
}
197+
}
198+
};
199+
reader.onerror = (e) => {
200+
console.log("err", e);
201+
};
202+
reader.readAsArrayBuffer(file);
203+
},
204+
},
205+
}).mount('#app')
206+
</script>
207+
208+
</body>
209+
210+
</html>

0 commit comments

Comments
 (0)