-
Notifications
You must be signed in to change notification settings - Fork 5
Subset API #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Subset API #65
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,3 +9,4 @@ luacov.stats.out | |
| .luacheckcache | ||
| api | ||
| *.src.rock | ||
| docs | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,9 @@ | ||
| project = 'luaharfbuzz' | ||
| description = 'Lua bindings to Harfbuzz' | ||
| full_description = 'The documentation is available on the @{https://github.com/harfbuzz/luaharfbuzz/wiki|wiki}' | ||
| full_description = 'The documentation is available at https://github.com/harfbuzz/luaharfbuzz/wiki' | ||
| use_markdown_titles = true | ||
| backtick_references = false | ||
| title = 'luaharfbuzz Documentation' | ||
| file = 'src/harfbuzz.luadoc' | ||
| file = {'src/harfbuzz.luadoc', 'src/harfbuzz-subset.luadoc'} | ||
| examples = 'examples' | ||
| format = 'markdown' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,9 @@ version = "scm-1" | |
| source = { | ||
| url = "git://github.com/harfbuzz/luaharfbuzz" | ||
| } | ||
| dependencies = { | ||
| "lua >= 5.3" | ||
| } | ||
| description = { | ||
| summary = "Lua bindings for the Harfbuzz text shaping library", | ||
| homepage = "https://github.com/harfbuzz/luaharfbuzz", | ||
|
|
@@ -12,33 +15,41 @@ description = { | |
| issues_url = "https://github.com/harfbuzz/luaharfbuzz/issues", | ||
| labels = {"unicode", "fonts"} | ||
| } | ||
| dependencies = { | ||
| "lua >= 5.3" | ||
| } | ||
| build = { | ||
| type = "builtin", | ||
| modules = { | ||
| harfbuzz ="src/harfbuzz.lua", | ||
| luaharfbuzz= { | ||
| sources = { | ||
| "src/luaharfbuzz/luaharfbuzz.c", | ||
| "src/luaharfbuzz/blob.c", | ||
| "src/luaharfbuzz/face.c", | ||
| "src/luaharfbuzz/font.c", | ||
| "src/luaharfbuzz/buffer.c", | ||
| "src/luaharfbuzz/feature.c", | ||
| "src/luaharfbuzz/tag.c", | ||
| "src/luaharfbuzz/ot.c", | ||
| "src/luaharfbuzz/unicode.c", | ||
| "src/luaharfbuzz/script.c", | ||
| "src/luaharfbuzz/direction.c", | ||
| "src/luaharfbuzz/language.c", | ||
| "src/luaharfbuzz/variation.c", | ||
| "src/luaharfbuzz/class_utils.c" | ||
| "src/luaharfbuzz/luaharfbuzz.c", | ||
| "src/luaharfbuzz/blob.c", | ||
| "src/luaharfbuzz/face.c", | ||
| "src/luaharfbuzz/font.c", | ||
| "src/luaharfbuzz/buffer.c", | ||
| "src/luaharfbuzz/feature.c", | ||
| "src/luaharfbuzz/tag.c", | ||
| "src/luaharfbuzz/ot.c", | ||
| "src/luaharfbuzz/unicode.c", | ||
| "src/luaharfbuzz/set.c", | ||
| "src/luaharfbuzz/script.c", | ||
| "src/luaharfbuzz/direction.c", | ||
| "src/luaharfbuzz/language.c", | ||
| "src/luaharfbuzz/variation.c", | ||
| "src/class_utils.c", | ||
| }, | ||
| libraries = {"harfbuzz"}, | ||
| incdirs = {"$(HARFBUZZ_INCDIR)/harfbuzz"}, | ||
| libdirs = {"$(HARFBUZZ_LIBDIR)"} | ||
| }, | ||
| luaharfbuzzsubset = { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we now have two separate libraries? I don’t mind either way, though I though we decided earlier to keep it as one library.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand the comment at #65 (comment) "The subset API should be in a separate Lua module, since not all users (especially LuaTeX) will need the subset API." in a way that two libraries should be created. One would be easier to deploy of course. I wouldn't mind to merge both into one library.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m OK with it this way. We can change it later if, say, integration in TeX Live turned out to be harder. I took the opportunity to fix a few notpicks that I commented about. I have one more nitpick to fix and this should be ready. |
||
| sources = { | ||
| "src/luaharfbuzzsubset/luaharfbuzzsubset.c", | ||
| "src/luaharfbuzzsubset/subset-input.c", | ||
| "src/class_utils.c", | ||
| }, | ||
| libraries = {"harfbuzz-subset"}, | ||
| incdirs = {"$(HARFBUZZ_INCDIR)/harfbuzz"}, | ||
| libdirs = {"$(HARFBUZZ_LIBDIR)"} | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| package.path = "./src/?.lua;./src/?/init.lua;" .. package.path | ||
|
|
||
| local harfbuzzsubset = require("harfbuzzsubset") | ||
| local harfbuzz = require("harfbuzz") | ||
|
|
||
| describe("harfbuzz module", function() | ||
| it("can be required when subset module is also required", function() | ||
| assert.is_not_nil(harfbuzz) | ||
| assert.is_not_nil(harfbuzzsubset) | ||
| end) | ||
| end) | ||
|
|
||
|
|
||
| describe("harfbuzzsubset basic usage", function() | ||
| local hb, hb_subset | ||
| local font_path = "fonts/AdventPro-VariableFont_wdth,wght.ttf" | ||
|
|
||
| setup(function() | ||
| hb = require "harfbuzz" | ||
| hb_subset = require "harfbuzzsubset" | ||
| end) | ||
|
|
||
| it("returns a valid version string", function() | ||
| assert.are_equal("string", type(hb_subset.version())) | ||
| end) | ||
|
|
||
| it("creates a subset input object", function() | ||
| local input = hb_subset.subset_input.new() | ||
| assert.truthy(input) | ||
| -- minimal test of methods existing | ||
| assert.is_function(input.keep_everything) | ||
| assert.is_function(input.pin_axis_location) | ||
| end) | ||
|
|
||
| it("can subset a variable font without errors", function() | ||
| local face = hb.Face.new(font_path) | ||
| local wght = hb.Tag.new("wght") | ||
| local wdth = hb.Tag.new("wdth") | ||
|
|
||
| local input = hb_subset.subset_input.new() | ||
| input:pin_axis_location(face, wght, 100) | ||
| input:pin_axis_location(face, wdth, 100) | ||
| input:keep_everything() | ||
|
|
||
| local ok, new_face = pcall(function() | ||
| return hb_subset.subset(face, input) | ||
| end) | ||
|
|
||
| assert.is_true(ok) | ||
| assert.truthy(new_face) | ||
| assert.are_not.equal(face, new_face) | ||
| end) | ||
|
|
||
| it("produces a non-empty blob after subsetting", function() | ||
| local face = hb.Face.new(font_path) | ||
| local wght = hb.Tag.new("wght") | ||
| local wdth = hb.Tag.new("wdth") | ||
|
|
||
| local input = hb_subset.subset_input.new() | ||
| input:pin_axis_location(face, wght, 100) | ||
| input:pin_axis_location(face, wdth, 100) | ||
| input:keep_everything() | ||
|
|
||
| local new_face = hb_subset.subset(face, input) | ||
|
|
||
| local blob = new_face:blob() | ||
| assert.truthy(blob) | ||
| assert.is_function(blob.get_data) | ||
|
|
||
| local data = blob:get_data() | ||
| assert.is_string(data) | ||
| assert.is_true(#data > 0) | ||
| end) | ||
|
|
||
| it("preserves glyph count when keep_everything is used", function() | ||
| local face = hb.Face.new(font_path) | ||
| local orig_glyphs = face:get_glyph_count() | ||
|
|
||
| local wght = hb.Tag.new("wght") | ||
| local wdth = hb.Tag.new("wdth") | ||
| local input = hb_subset.subset_input.new() | ||
| input:pin_axis_location(face, wght, 100) | ||
| input:pin_axis_location(face, wdth, 100) | ||
| input:keep_everything() | ||
|
|
||
| local new_face = hb_subset.subset(face, input) | ||
| local subset_glyphs = new_face:get_glyph_count() | ||
|
|
||
| assert.equals(orig_glyphs, subset_glyphs) | ||
| end) | ||
|
|
||
| it("produces different blobs for different axis locations", function() | ||
| local face = hb.Face.new(font_path) | ||
| local wght = hb.Tag.new("wght") | ||
| local wdth = hb.Tag.new("wdth") | ||
|
|
||
| local input1 = hb_subset.subset_input.new() | ||
| input1:pin_axis_location(face, wght, 100) | ||
| input1:pin_axis_location(face, wdth, 100) | ||
| input1:keep_everything() | ||
| local face_light = hb_subset.subset(face, input1) | ||
| local blob_light = face_light:blob():get_data() | ||
|
|
||
| local input2 = hb_subset.subset_input.new() | ||
| input2:pin_axis_location(face, wght, 900) | ||
| input2:pin_axis_location(face, wdth, 100) | ||
| input2:keep_everything() | ||
| local face_bold = hb_subset.subset(face, input2) | ||
| local blob_bold = face_bold:blob():get_data() | ||
|
|
||
| assert.is_true( | ||
| blob_light ~= blob_bold, | ||
| ("expected different blobs for axis locations, but they are identical (len=%d bytes)") | ||
| :format(#blob_light) | ||
| ) | ||
| assert.is_true(#blob_light > 0, "blob_light is empty") | ||
| assert.is_true(#blob_bold > 0, "blob_bold is empty") | ||
| end) | ||
|
|
||
| it("reduces glyph count when subsetting to a single Unicode", function() | ||
| local face = hb.Face.new(font_path) | ||
| local orig_glyphs = face:get_glyph_count() | ||
|
|
||
| local input = hb_subset.subset_input.new() | ||
|
|
||
| local uset = input:unicode_set() | ||
| uset:add(0x41) -- oder string.byte("A") | ||
|
|
||
| local new_face = hb_subset.subset(face, input) | ||
| local subset_glyphs = new_face:get_glyph_count() | ||
|
|
||
| -- Sanity-Checks mit vernünftiger Fehlermeldung, ohne Binärmüll: | ||
| assert.is_true( | ||
| subset_glyphs > 0, | ||
| ("expected subset glyph count > 0, got %d"):format(subset_glyphs) | ||
| ) | ||
| assert.is_true( | ||
| subset_glyphs < orig_glyphs, | ||
| ("expected fewer glyphs after subsetting, got %d (orig %d)") | ||
| :format(subset_glyphs, orig_glyphs) | ||
| ) | ||
| end) | ||
| end) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #include <stdlib.h> | ||
| #include <stdio.h> | ||
| #include <assert.h> | ||
| #include <hb.h> | ||
| #include <hb-ot.h> | ||
| #include <string.h> | ||
|
|
||
| #include <lua.h> | ||
| #include <lauxlib.h> | ||
| #include <lualib.h> | ||
|
|
||
| typedef hb_blob_t* Blob; | ||
| typedef hb_face_t* Face; | ||
| typedef hb_font_t* Font; | ||
| typedef hb_buffer_t* Buffer; | ||
| typedef hb_set_t* Set; | ||
| typedef hb_feature_t Feature; | ||
| typedef hb_tag_t Tag; | ||
| typedef hb_script_t Script; | ||
| typedef hb_direction_t Direction; | ||
| typedef hb_language_t Language; | ||
| typedef hb_variation_t Variation; | ||
|
|
||
| typedef struct luahb_constant_t { | ||
| const char *name; | ||
| int value; | ||
| } luahb_constant_t; | ||
|
|
||
| // Functions to create classes and push them onto the stack | ||
| int register_class(lua_State *L, const char *name, const luaL_Reg * methods, const luaL_Reg *functions, const luahb_constant_t* constants); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| ----------- | ||
| -- Lua bindings to the Harfbuzz subset API. | ||
| -- * [Wiki](http://github.com/harfbuzz/luaharfbuzz/wiki) | ||
| -- * [Source on Github](https://github.com/harfbuzz/luaharfbuzz) | ||
| -- * [API Coverage Status](https://github.com/harfbuzz/luaharfbuzz/blob/master/status/done.txt) | ||
| -- | ||
| -- This module provides bindings to the HarfBuzz `hb-subset` API. It is | ||
| -- designed to be used together with the main @{harfbuzz} module. | ||
| -- | ||
| -- Typical usage: | ||
| -- | ||
| -- local hb = require('harfbuzz') | ||
| -- local hb_subset = require("harfbuzzsubset") | ||
| -- | ||
| -- local input = hb_subset.subset.create() | ||
| -- | ||
| -- local wght = hb.Tag.new("wght") | ||
| -- local wdth = hb.Tag.new("wdth") | ||
| -- local face = hb.Face.new("myfont.ttf") | ||
| -- | ||
| -- input:pin_axis_location(face,wght, 100) | ||
| -- input:pin_axis_location(face,wdth, 100) | ||
| -- | ||
| -- input:keep_everything() | ||
| -- new_face = hb_subset.subset(face, input) | ||
| -- | ||
| -- local blob = new_face:blob() | ||
| -- local fh, err = io.open("out.ttf", "wb") | ||
| -- if not fh then error(err) end | ||
| -- fh:write(blob:get_data()) | ||
| -- fh:close() | ||
| -- | ||
| -- The module returned by `require "harfbuzzsubset"` has two fields: | ||
| -- | ||
| -- * `subset_input` — helpers and methods related to @{SubsetInput} | ||
| -- * `subset` — helpers and methods related to @{SubsetInput} | ||
| -- | ||
| -- @author Patrick Gundlach <<gundlach@speedata.de>> | ||
| -- @copyright 2025 | ||
| -- @license MIT | ||
| -- @module harfbuzzsubset | ||
|
|
||
|
|
||
| --- Wraps `hb_subset_or_fail`. | ||
| -- | ||
| -- Performs the actual subset operation, producing a new `Face` that only | ||
| -- contains the glyphs and data configured on this input. | ||
| -- | ||
| -- @param face source `Face` object to subset. | ||
| -- @param input `SubsetInput` object to subset based on. | ||
| -- @return new `Face` object representing the subset, or `nil` on failure. | ||
| -- @function subset | ||
|
|
||
|
|
||
| --- Wraps `hb_version`. | ||
| -- @function version | ||
|
|
||
| --- Lua wrapper for `hb_subset_input_t` type. | ||
| -- | ||
| -- Objects of this type control which parts of a `Face` are kept when | ||
| -- creating a subset. | ||
| -- | ||
| -- Instances are usually created via @{subset_input.new}. | ||
| -- | ||
| -- @type SubsetInput | ||
|
|
||
|
|
||
| --- Wraps `hb_subset_input_create_or_fail`. | ||
| -- | ||
| -- Initializes a new `hb_subset_input_t`. The returned object can be used to | ||
| -- configure which glyphs and features are kept when subsetting a face. | ||
| -- | ||
| -- @return `SubsetInput` object, or `nil` on failure. | ||
| -- @function subset_input.new | ||
|
|
||
|
|
||
| --- Wraps `hb_subset_input_unicode_set`. | ||
| -- | ||
| -- Returns the Unicode set associated with this subset input. The returned | ||
| -- @{harfbuzz.Set} can be modified (e.g. via @{harfbuzz.Set:add}) to control which | ||
| -- Unicode codepoints are included in the subset. | ||
| -- | ||
| -- @return `Set` object representing the Unicode set, or `nil` on failure. | ||
| -- @function SubsetInput:unicode_set | ||
|
|
||
|
|
||
| --- Wraps `hb_subset_input_pin_axis_location`. | ||
| -- | ||
| -- Pins a variation axis at a specific value for subsetting. | ||
| -- | ||
| -- @param face `Face` object whose design space is being subset. | ||
| -- @param tag `Tag` object representing the axis tag (e.g. `"wght"`, `"wdth"`). | ||
| -- @param value numeric axis value to pin. | ||
| -- @return `true` on success, `false` on failure. | ||
| -- @function SubsetInput:pin_axis_location | ||
|
|
||
|
|
||
| --- Wraps `hb_subset_input_keep_everything`. | ||
| -- | ||
| -- Configures the input so that everything in the original face is kept. | ||
| -- This is useful as a starting point before applying more specific filters. | ||
| -- | ||
| -- @function SubsetInput:keep_everything | ||
|
|
||
|
|
||
| --- Wraps `hb_subset_input_destroy`. | ||
| -- | ||
| -- Destructor for subset inputs. Normally this is invoked automatically by | ||
| -- the garbage collector; applications rarely need to call it directly. | ||
| -- | ||
| -- @function SubsetInput:__gc |
Uh oh!
There was an error while loading. Please reload this page.