Skip to content

Commit 926759d

Browse files
committed
feat: remove woff ts impl
1 parent 87f3602 commit 926759d

File tree

6 files changed

+156
-258
lines changed

6 files changed

+156
-258
lines changed

bindings/javascript/wasm.ts

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export interface WASMInstance {
4040
) => number
4141
save_subset_to_file: (subset_handle: number, path_ptr: number, path_len: number) => number
4242

43+
convert_ttf_to_woff: (font_ptr: number, font_len: number, output_ptr: number, output_len: number) => number
44+
convert_ttf_to_woff_from_reader: (reader_handle: number, output_ptr: number, output_len: number) => number
45+
4346
get_error_message_length: (error_code: number) => number
4447
get_error_message: (error_code: number, buffer_ptr: number, buffer_len: number) => number
4548
}
@@ -48,6 +51,7 @@ export interface WASMZigEnv extends WebAssembly.ModuleImports {
4851
host_read_file: (pathPtr: number, pathLen: number, outPtrPtr: number, outLenPtr: number) => boolean
4952
host_write_file: (pathPtr: number, pathLen: number, dataPtr: number, dataLen: number) => boolean
5053
host_head_modified_time: (outTimestampPtr: number) => boolean
54+
host_compress_data: (inputPtr: number, inputLen: number, outputPtrPtr: number, outputLenPtr: number) => boolean
5155
}
5256

5357
const MAC_EPOCH_OFFSET = 2082844800 // Offset from Unix epoch to Mac epoch
@@ -59,7 +63,8 @@ export const ERR_CODE = {
5963
PARSE_FAILED: 3,
6064
INVALID_UTF8: 4,
6165
MISSING_TABLE: 5,
62-
OUT_OF_BOUNDS: 6
66+
OUT_OF_BOUNDS: 6,
67+
COMPRESSION_FAILED: 7
6368
} as const
6469

6570
export interface FontMetrics {
@@ -87,9 +92,13 @@ const IS_NODE_PLATFORM = typeof process !== 'undefined' && process.versions?.nod
8792
let FS_WRAP: Promise<typeof import('fs')> | null = null
8893
let fs: typeof import('fs') | null = null
8994

95+
let ZLIB_WRAP: Promise<typeof import('zlib')> | null = null
96+
let zlib: typeof import('zlib') | null = null
97+
9098
if (IS_NODE_PLATFORM) {
9199
try {
92100
FS_WRAP = import('fs').then((fsModule) => fsModule.default || fsModule)
101+
ZLIB_WRAP = import('zlib').then((zlibModule) => zlibModule.default || zlibModule)
93102
} catch {
94103
}
95104
}
@@ -107,6 +116,51 @@ export class FontSubset {
107116
this.instance = instance
108117
}
109118

119+
static async convertTtfToWoff(instance: WASMInstance, fontData: Uint8Array) {
120+
if (!zlib && IS_NODE_PLATFORM && ZLIB_WRAP) {
121+
zlib = await ZLIB_WRAP
122+
}
123+
const fontPtr = instance.allocate_memory(fontData.length)
124+
if (fontPtr === null) { return null }
125+
126+
const outputPtrPtr = instance.allocate_memory(4)
127+
const outputLenPtr = instance.allocate_memory(4)
128+
129+
if (outputPtrPtr === null || outputLenPtr === null) {
130+
instance.free_memory(fontPtr, fontData.length)
131+
if (outputPtrPtr) { instance.free_memory(outputPtrPtr, 4) }
132+
if (outputLenPtr) { instance.free_memory(outputLenPtr, 4) }
133+
return null
134+
}
135+
136+
try {
137+
const memory = new Uint8Array(instance.memory.buffer)
138+
memory.set(fontData, fontPtr)
139+
140+
const errorCode = instance.convert_ttf_to_woff(fontPtr, fontData.length, outputPtrPtr, outputLenPtr)
141+
142+
if (errorCode !== ERR_CODE.SUCCESS) {
143+
return null
144+
}
145+
146+
const outputPtr = new Uint32Array(instance.memory.buffer, outputPtrPtr, 1)[0]
147+
const outputLen = new Uint32Array(instance.memory.buffer, outputLenPtr, 1)[0]
148+
149+
if (outputLen === 0) { return new Uint8Array(0) }
150+
151+
const result = new Uint8Array(instance.memory.buffer, outputPtr, outputLen)
152+
const copy = new Uint8Array(result)
153+
154+
instance.free_memory(outputPtr, outputLen)
155+
156+
return copy
157+
} finally {
158+
instance.free_memory(fontPtr, fontData.length)
159+
instance.free_memory(outputPtrPtr, 4)
160+
instance.free_memory(outputLenPtr, 4)
161+
}
162+
}
163+
110164
static createSubsetFromText(instance: WASMInstance, fontData: Uint8Array, text: string): Uint8Array | null {
111165
const fontPtr = instance.allocate_memory(fontData.length)
112166
if (fontPtr === null) { return null }
@@ -504,6 +558,34 @@ export function createSubsetEngine(binary: Uint8Array): FontSubset {
504558
} catch {
505559
return false
506560
}
561+
},
562+
host_compress_data(inputPtr, inputLen, outputPtrPtr, outputLenPtr) {
563+
try {
564+
if (!WASM_INSTANCE) { return false }
565+
if (IS_NODE_PLATFORM && zlib) {
566+
const memory = new Uint8Array(WASM_INSTANCE.memory.buffer)
567+
const inputData = memory.slice(inputPtr, inputPtr + inputLen)
568+
569+
const compressedData = zlib.deflateSync(inputData)
570+
571+
const outputPtr = WASM_INSTANCE.allocate_memory(compressedData.length)
572+
if (outputPtr === null) { return false }
573+
574+
const outputMemory = new Uint8Array(WASM_INSTANCE.memory.buffer)
575+
outputMemory.set(compressedData, outputPtr)
576+
577+
const outPtrView = new Uint32Array(WASM_INSTANCE.memory.buffer, outputPtrPtr, 1)
578+
const outLenView = new Uint32Array(WASM_INSTANCE.memory.buffer, outputLenPtr, 1)
579+
580+
outPtrView[0] = outputPtr
581+
outLenView[0] = compressedData.length
582+
583+
return true
584+
}
585+
return false
586+
} catch {
587+
return false
588+
}
507589
}
508590
}
509591
WASM_INSTANCE = new WebAssembly.Instance(compiledWASM, { env }).exports as unknown as WASMInstance
@@ -518,3 +600,8 @@ export function createSubsetFromText(
518600
if (!WASM_INSTANCE) { return null }
519601
return FontSubset.createSubsetFromText(WASM_INSTANCE, fontData, text)
520602
}
603+
604+
export async function convertTtfToWoff(fontData: Uint8Array) {
605+
if (!WASM_INSTANCE) { return null }
606+
return FontSubset.convertTtfToWoff(WASM_INSTANCE, fontData)
607+
}

bindings/wasm/mod.zig

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const std = @import("std");
22
const lib = @import("ttf");
33

4+
const woff = lib.woff;
45
var gpa = std.heap.wasm_allocator;
56

67
const SubsetHandle = opaque {};
@@ -14,6 +15,7 @@ pub const ErrorCode = enum(u32) {
1415
invalid_utf8 = 4,
1516
missing_table = 5,
1617
out_of_bounds = 6,
18+
compression_failed = 7,
1719
};
1820

1921
export fn allocate_memory(size: u32) ?[*]u8 {
@@ -44,6 +46,58 @@ extern "env" fn host_write_file(path_ptr: [*]const u8, path_len: u32, data_ptr:
4446

4547
extern "env" fn host_head_modified_time(out_timestamp: *u64) bool;
4648

49+
extern "env" fn host_compress_data(input_ptr: [*]const u8, input_len: u32, output_ptr: *[*]u8, output_len: *u32) bool;
50+
51+
fn wasm_compressor(allocator: std.mem.Allocator, data: []const u8) ![]u8 {
52+
var output_ptr: [*]u8 = undefined;
53+
var output_len: u32 = undefined;
54+
55+
if (!host_compress_data(data.ptr, @intCast(data.len), &output_ptr, &output_len)) {
56+
return woff.woff_impl.Error.CompressionFailed;
57+
}
58+
59+
const result = try allocator.alloc(u8, output_len);
60+
@memcpy(result, output_ptr[0..output_len]);
61+
62+
free_memory(output_ptr, output_len);
63+
64+
return result;
65+
}
66+
67+
export fn convert_ttf_to_woff(font_ptr: [*]const u8, font_len: u32, output_ptr: *[*]u8, output_len: *u32) ErrorCode {
68+
const font_data = font_ptr[0..font_len];
69+
70+
const woff_data = woff.woff_v1.ttf_to_woff_v1(gpa, @constCast(font_data), wasm_compressor) catch |err| {
71+
return switch (err) {
72+
error.OutOfMemory => .allocation_failed,
73+
error.CompressionFailed => .compression_failed,
74+
else => .parse_failed,
75+
};
76+
};
77+
78+
output_ptr.* = woff_data.ptr;
79+
output_len.* = @intCast(woff_data.len);
80+
81+
return .success;
82+
}
83+
84+
export fn convert_ttf_to_woff_from_reader(reader_handle: ?*FontReaderHandle, output_ptr: *[*]u8, output_len: *u32) ErrorCode {
85+
const reader: *lib.FontReader = @ptrCast(@alignCast(reader_handle orelse return .invalid_pointer));
86+
87+
const woff_data = woff.woff_v1.ttf_woff_v1_with_parser(gpa, &reader.subset.parser, wasm_compressor) catch |err| {
88+
return switch (err) {
89+
error.OutOfMemory => .allocation_failed,
90+
error.CompressionFailed => .compression_failed,
91+
else => .parse_failed,
92+
};
93+
};
94+
95+
output_ptr.* = woff_data.ptr;
96+
output_len.* = @intCast(woff_data.len);
97+
98+
return .success;
99+
}
100+
47101
export fn load_font_from_file(path_ptr: [*]const u8, path_len: u32) ?*FontReaderHandle {
48102
var data_ptr: [*]u8 = undefined;
49103
var data_len: u32 = undefined;
@@ -278,6 +332,7 @@ export fn get_error_message_length(error_code: ErrorCode) u32 {
278332
.invalid_utf8 => "Invalid UTF-8 text",
279333
.missing_table => "Required font table missing",
280334
.out_of_bounds => "Index out of bounds",
335+
.compression_failed => "Compression failed",
281336
};
282337
return @intCast(message.len);
283338
}
@@ -291,6 +346,7 @@ export fn get_error_message(error_code: ErrorCode, buffer_ptr: [*]u8, buffer_len
291346
.invalid_utf8 => "Invalid UTF-8 text",
292347
.missing_table => "Required font table missing",
293348
.out_of_bounds => "Index out of bounds",
349+
.compression_failed => "Compression failed",
294350
};
295351

296352
if (buffer_len < message.len) return .out_of_bounds;

npm/cli/src/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { readFileSync, statSync, writeFileSync } from 'fs'
55
import mri from 'mri'
66
import { extname, resolve } from 'path'
77
import { globSync } from 'tinyglobby'
8-
import { ttf } from 'ttf.zig'
9-
import { ttf2woff } from './woff'
8+
import { convertTtfToWoff, ttf } from 'ttf.zig'
9+
// import { ttf2woff } from './woff'
1010

1111
interface CliOptions {
1212
content?: string[]
@@ -150,7 +150,7 @@ function formatFileSize(bytes: number): string {
150150
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
151151
}
152152

153-
function main() {
153+
async function main() {
154154
const args = mri<CliOptions>(process.argv.slice(2), {
155155
alias: {
156156
c: 'content',
@@ -265,7 +265,11 @@ function main() {
265265

266266
if (outputPath.endsWith('.woff')) {
267267
logVerbose(`Converting subset font to WOFF format for ${outputPath}...`, options.verbose || false)
268-
outputData = ttf2woff(subsetFont)
268+
const woffData = await convertTtfToWoff(subsetFont)
269+
if (!woffData) {
270+
throw new Error(`Failed to convert TTF to WOFF for ${outputPath}`)
271+
}
272+
outputData = woffData
269273
} else {
270274
outputData = subsetFont
271275
}
@@ -290,4 +294,4 @@ function main() {
290294
}
291295
}
292296

293-
main()
297+
main().catch(() => {})

0 commit comments

Comments
 (0)