diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 43ddffb41..9b09df10a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,11 +1,7 @@ name: Test on: - push: - paths-ignore: - - ".github/workflows/prebuild.yaml" - pull_request: - paths-ignore: - - ".github/workflows/prebuild.yaml" + - push + - pull_request jobs: Linux: @@ -19,12 +15,11 @@ jobs: with: node-version: ${{ matrix.node }} - uses: actions/checkout@v4 - - name: Install Dependencies - run: | - sudo apt update - sudo apt install -y libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev librsvg2-dev + - uses: mlugg/setup-zig@v2 - name: Install - run: npm ci --build-from-source + run: npm ci --force # https://github.com/npm/cli/issues/6138 + - name: Build + run: zig build - name: Test run: npm test @@ -39,15 +34,11 @@ jobs: with: node-version: ${{ matrix.node }} - uses: actions/checkout@v4 - - name: Install Dependencies - run: | - Invoke-WebRequest "https://ftp.gnome.org/pub/GNOME/binaries/win64/gtk+/2.22/gtk+-bundle_2.22.1-20101229_win64.zip" -OutFile "gtk.zip" - Expand-Archive gtk.zip -DestinationPath "C:\GTK" - Invoke-WebRequest "https://downloads.sourceforge.net/project/libjpeg-turbo/2.0.4/libjpeg-turbo-2.0.4-vc64.exe" -OutFile "libjpeg.exe" -UserAgent NativeHost - .\libjpeg.exe /S - choco install vcredist2010 -y + - uses: mlugg/setup-zig@v2 - name: Install - run: npm ci --build-from-source + run: npm ci --force + - name: Build + run: zig build - name: Test run: npm test @@ -62,12 +53,11 @@ jobs: with: node-version: ${{ matrix.node }} - uses: actions/checkout@v4 - - name: Install Dependencies - run: | - brew update - brew install python-setuptools pkg-config cairo pango libpng jpeg giflib librsvg + - uses: mlugg/setup-zig@v2 - name: Install - run: npm ci --build-from-source + run: npm ci --force + - name: Build + run: zig build - name: Test run: npm test @@ -80,7 +70,7 @@ jobs: node-version: 20.9.0 - uses: actions/checkout@v4 - name: Install - run: npm ci --ignore-scripts + run: npm ci --force - name: Lint run: npm run lint - name: Lint Types diff --git a/.github/workflows/prebuild.yaml b/.github/workflows/prebuild.yaml deleted file mode 100644 index 88b6288bf..000000000 --- a/.github/workflows/prebuild.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# This is a dummy file so that this workflow shows up in the Actions tab. -# Prebuilds are actually run using the prebuilds branch. - -name: Make Prebuilds -on: workflow_dispatch - -jobs: - Linux: - name: Nothing - runs-on: ubuntu-latest - steps: - - name: Nothing - run: echo "Nothing to do here" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index bd641c145..48753e65d 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -1,13 +1,50 @@ -# This is a dummy file so that this workflow shows up in the Actions tab. -# Publishing packages via CI is a 4x feature (next branch) not yet released - name: Publish on: workflow_dispatch jobs: - Linux: - name: Nothing + binaries: + name: ${{ matrix.package }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - { runner: macos-15, target: aarch64-macos, package: canvas-darwin-arm64 } + - { runner: macos-15, target: x86_64-macos, package: canvas-darwin-x64 } + - { runner: ubuntu-latest, target: x86-windows, package: canvas-win32-x86 } + - { runner: ubuntu-latest, target: x86_64-windows, package: canvas-win32-x64 } + - { runner: ubuntu-latest, target: aarch64-windows, package: canvas-win32-arm64 } + - { runner: ubuntu-latest, target: arm-linux-gnueabihf, package: canvas-linux-arm-gnueabihf } + - { runner: ubuntu-latest, target: x86-linux-gnu.2.28, package: canvas-linux-x86-gnu } + - { runner: ubuntu-latest, target: x86-linux-musl, package: canvas-linux-x86-musl } + - { runner: ubuntu-latest, target: x86_64-linux-gnu.2.28, package: canvas-linux-x64-gnu } + - { runner: ubuntu-latest, target: x86_64-linux-musl, package: canvas-linux-x64-musl } + - { runner: ubuntu-latest, target: aarch64-linux-gnu.2.28, package: canvas-linux-arm64-gnu } + - { runner: ubuntu-latest, target: aarch64-linux-musl, package: canvas-linux-arm64-musl } + - { runner: ubuntu-latest, target: riscv64-linux-gnu, package: canvas-linux-riscv64-gnu } + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 26 + - uses: mlugg/setup-zig@v2 + - name: Install + run: npm ci --force # https://github.com/npm/cli/issues/6138 + - name: Build + run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseSafe + - name: Publish + run: npm publish --dry-run --workspace ${{ matrix.package }} + + root: + name: canvas + needs: binaries runs-on: ubuntu-latest steps: - - name: Nothing - run: echo "Nothing to do here" + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 26 + - name: Install + run: npm ci --force # https://github.com/npm/cli/issues/6138 + - name: Publish + run: npm publish --dry-run diff --git a/.gitignore b/.gitignore index 4dc95228d..e285ad546 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -build +bin +canvas.node .DS_Store .lock-wscript test/images/*.png @@ -18,3 +19,6 @@ node_modules npm-debug.log .idea + +.zig-cache/ +zig-pkg/ diff --git a/README.md b/README.md index d02b3bfe0..055a8bf43 100644 --- a/README.md +++ b/README.md @@ -11,34 +11,16 @@ node-canvas is a [Cairo](http://cairographics.org/)-backed Canvas implementation $ npm install canvas ``` -By default, pre-built binaries will be downloaded if you're on one of the following platforms: -- macOS x86/64 -- macOS aarch64 (aka Apple silicon) -- Linux x86/64 (glibc only) -- Windows x86/64 - -If you want to build from source, use `npm install --build-from-source` and see the **Compiling** section below. - The minimum version of Node.js required is **18.12.0**. -### Compiling - -If you don't have a supported OS or processor architecture, or you use `--build-from-source`, the module will be compiled on your system. This requires several dependencies, including Cairo and Pango. - -For detailed installation information, see the [wiki](https://github.com/Automattic/node-canvas/wiki/_pages). One-line installation instructions for common OSes are below. Note that libgif/giflib, librsvg and libjpeg are optional and only required if you need GIF, SVG and JPEG support, respectively. Cairo v1.10.0 or later is required. - -OS | Command ------ | ----- -macOS | Using [Homebrew](https://brew.sh/):
`brew install pkg-config cairo pango libpng jpeg giflib librsvg pixman python-setuptools` -Ubuntu | `sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev` -Fedora | `sudo yum install gcc-c++ cairo-devel pango-devel libjpeg-turbo-devel giflib-devel` -Solaris | `pkgin install cairo pango pkg-config xproto renderproto kbproto xextproto` -OpenBSD | `doas pkg_add cairo pango png jpeg giflib` -Windows | See the [wiki](https://github.com/Automattic/node-canvas/wiki/Installation:-Windows) -Others | See the [wiki](https://github.com/Automattic/node-canvas/wiki) +Binaries will be installed for the following platforms: +- macOS x64/arm64 +- Linux x64/x86/arm64 (glibc or musl) +- Linux arm/riscv64 (glibc) +- Windows x64/x86/arm64 -**Mac OS X v10.11+:** If you have recently updated to Mac OS X v10.11+ and are experiencing trouble when compiling, run the following command: `xcode-select --install`. Read more about the problem [on Stack Overflow](http://stackoverflow.com/a/32929012/148072). -If you have xcode 10.0 or higher installed, in order to build from source you need NPM 6.4.1 or higher. +If you want to install for a more exotic system, CD into the package and run +`zig build`. You may need to tweak build.zig. PRs are welcome. ## Quick Example @@ -77,9 +59,11 @@ This project is an implementation of the Web Canvas API and implements that API * [createCanvas()](#createcanvas) * [createImageData()](#createimagedata) * [loadImage()](#loadimage) -* [registerFont()](#registerfont) -* [deregisterAllFonts()](#deregisterAllFonts) +### Font API + +* [FontFace](#fontface) +* [fonts](#fonts) ### Non-standard APIs @@ -151,17 +135,35 @@ const myimg = await loadImage('http://server.com/image.png') // do something with image ``` -### registerFont() +### FontFace > ```ts -> registerFont(path: string, { family: string, weight?: string, style?: string }) => void +> +> interface FontFaceDescriptors { +> weight?: string | number; +> style?: string; +> } +> +> class FontFace { +> constructor( +> family: string, +> source: string | ArrayBuffer | Uint8Array, +> descriptors?: FontFaceDescriptors +> ) +> +> family: string; +> style: string; +> weight: string; +> status: 'loaded' | 'unloaded' | 'error'; +> } +> > ``` -To use a font file that is not installed as a system font, use `registerFont()` to register the font with Canvas. +To use your own font file, create a `FontFace` with the file path and add it to [`fonts`](#fonts). `fonts` is just like the browser's `document.fonts`, but with a slightly more limited API. ```js -const { registerFont, createCanvas } = require('canvas') -registerFont('comicsans.ttf', { family: 'Comic Sans' }) +const { createCanvas, FontFace, fonts } = require('canvas') +fonts.add(new FontFace('Comic Sans', '/home/you/comicsans.ttf')) const canvas = createCanvas(500, 500) const ctx = canvas.getContext('2d') @@ -170,34 +172,44 @@ ctx.font = '12px "Comic Sans"' ctx.fillText('Everyone hates this font :(', 250, 10) ``` -The second argument is an object with properties that resemble the CSS properties that are specified in `@font-face` rules. You must specify at least `family`. `weight`, and `style` are optional and default to `'normal'`. +The second argument can either be a path to the font file or a buffer in TrueType format. The third argument is an object with properties that resemble the CSS properties that are specified in `@font-face` rules. You must specify at least `family`. `weight` and `style` are optional and default to `'normal'`. -### deregisterAllFonts() +### fonts > ```ts -> deregisterAllFonts() => void +> +> class FontFaceSet { +> add(face: FontFace): void; +> has(face: FontFace): boolean; +> clear(): void; +> delete(face: FontFace): boolean; +> [Symbol.iterator](): Iterator; +> size: number; +> } +> +> fonts: FontFaceSet > ``` -Use `deregisterAllFonts` to unregister all fonts that have been previously registered. This method is useful when you want to remove all registered fonts, such as when using the canvas in tests +`fonts` implements a subset of the browser's `document.fonts`. Use `fonts.delete()` to unregister an individual font, or `fonts.clear()` to remove all registered fonts. This is useful when you want to remove all registered fonts, such as when using the canvas in tests. ```ts -const { registerFont, createCanvas, deregisterAllFonts } = require('canvas') +const { registerFont, createCanvas, fonts, FontFace } = require('canvas') describe('text rendering', () => { - afterEach(() => { - deregisterAllFonts(); - }) - it('should render text with Comic Sans', () => { - registerFont('comicsans.ttf', { family: 'Comic Sans' }) - - const canvas = createCanvas(500, 500) - const ctx = canvas.getContext('2d') - - ctx.font = '12px "Comic Sans"' - ctx.fillText('Everyone loves this font :)', 250, 10) - - // assertScreenshot() - }) + afterEach(() => { + fonts.clear() + }) + it('should render text with Comic Sans', () => { + fonts.add(new FontFace('Comic Sans', '/home/you/comicsans.ttf')) + + const canvas = createCanvas(500, 500) + const ctx = canvas.getContext('2d') + + ctx.font = '12px "Comic Sans"' + ctx.fillText('Everyone loves this font :)', 250, 10) + + // assertScreenshot() + }) }) ``` @@ -271,7 +283,7 @@ Enabling mime data tracking has no benefits (only a slow down) unless you are ge Creates a [`Buffer`](https://nodejs.org/api/buffer.html) object representing the image contained in the canvas. * **callback** If provided, the buffer will be provided in the callback instead of being returned by the function. Invoked with an error as the first argument if encoding failed, or the resulting buffer as the second argument if it succeeded. Not supported for mimeType `raw` or for PDF or SVG canvases. -* **mimeType** A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded data in BGRA order on little-endian (most) systems, ARGB on big-endian systems; top-to-bottom), `application/pdf` (for PDF canvases) and `image/svg+xml` (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas. +* **mimeType** A string indicating the image format. Valid options are `image/png`, `image/jpeg`, `raw` (unencoded data in BGRA order on little-endian (most) systems, ARGB on big-endian systems; top-to-bottom), `application/pdf` (for PDF canvases) and `image/svg+xml` (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas. * **config** * For `image/jpeg`, an object specifying the quality (0 to 1), if progressive compression should be used and/or if chroma subsampling should be used: `{quality: 0.75, progressive: false, chromaSubsampling: true}`. All properties are optional. @@ -279,7 +291,7 @@ Creates a [`Buffer`](https://nodejs.org/api/buffer.html) object representing the Note that the PNG format encodes the resolution in pixels per meter, so if you specify `96`, the file will encode 3780 ppm (~96.01 ppi). The resolution is undefined by default to match common browser behavior. - * For `application/pdf`, an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. All properties are optional and default to `undefined`, except for `creationDate`, which defaults to the current date. *Adding metadata requires Cairo 1.16.0 or later.* + * For `application/pdf`, an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. All properties are optional and default to `undefined`, except for `creationDate`, which defaults to the current date. For a description of these properties, see page 550 of [PDF 32000-1:2008](https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf). @@ -403,7 +415,7 @@ const stream = canvas.createJPEGStream({ > canvas.createPDFStream(config?: any) => ReadableStream > ``` -* `config` an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. See `toBuffer()` for more information. *Adding metadata requires Cairo 1.16.0 or later.* +* `config` an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. See `toBuffer()` for more information. Applies to PDF canvases only. Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) that emits the encoded PDF. `canvas.toBuffer()` also produces an encoded PDF, but `createPDFStream()` can be used to reduce memory usage. @@ -491,7 +503,7 @@ ctx.fillText('Hello World 2', 50, 80) canvas.toBuffer() // returns a PDF file canvas.createPDFStream() // returns a ReadableStream that emits a PDF -// With optional document metadata (requires Cairo 1.16.0): +// With optional document metadata: canvas.toBuffer('application/pdf', { title: 'my picture', keywords: 'node.js demo cairo', @@ -575,7 +587,7 @@ These additional pixel formats have experimental support: * `RGB16_565` Each pixel is 16 bits, with red in the upper 5 bits, green in the middle 6 bits, and blue in the lower 5 bits, in native platform endianness. Some hardware devices and frame buffers use this format. Note that PNG does not support this format; when creating a PNG, the image will be converted to 24-bit RGB. This format is thus suboptimal for generating PNGs. `ImageData` instances for this mode use a `Uint16Array` instead of a `Uint8ClampedArray`. * `A1` Each pixel is 1 bit, and pixels are packed together into 32-bit quantities. The ordering of the bits matches the endianness of the platform: on a little-endian machine, the first pixel is the least-significant bit. This format can be used for creating single-color images. *Support for this format is incomplete, see note below.* -* `RGB30` Each pixel is 30 bits, with red in the upper 10, green in the middle 10, and blue in the lower 10. (Requires Cairo 1.12 or later.) *Support for this format is incomplete, see note below.* +* `RGB30` Each pixel is 30 bits, with red in the upper 10, green in the middle 10, and blue in the lower 10. *Support for this format is incomplete, see note below.* Notes and caveats: @@ -592,23 +604,23 @@ Notes and caveats: * The `ImageData(width, height)` and `ImageData(Uint8ClampedArray, width)` constructors assume 4 bytes per pixel. To create an `ImageData` instance with a different number of bytes per pixel, use `new ImageData(new Uint8ClampedArray(size), width, height)` or `new ImageData(new Uint16ClampedArray(size), width, height)`. -## Testing +## Local Development and Testing -First make sure you've built the latest version. Get all the deps you need (see [compiling](#compiling) above), and run: +To set up node-canvas for local develoment, use the `--force` argument (until [npm/cli#6138](https://github.com/npm/cli/issues/6138) is fixed): ``` -npm install --build-from-source +npm install --force ``` For visual tests: `npm run test-server` and point your browser to http://localhost:4000. For unit tests: `npm run test`. -## Benchmarks +### Benchmarks Benchmarks live in the `benchmarks` directory. -## Examples +### Examples Examples line in the `examples` directory. Most produce a png image of the same name, and others such as *live-clock.js* launch an HTTP server to be viewed in the browser. diff --git a/binding.gyp b/binding.gyp deleted file mode 100644 index 495f6e174..000000000 --- a/binding.gyp +++ /dev/null @@ -1,233 +0,0 @@ -{ - 'conditions': [ - ['OS=="win"', { - 'variables': { - 'GTK_Root%': 'C:/GTK', # Set the location of GTK all-in-one bundle - 'with_jpeg%': 'false', - 'with_gif%': 'false', - 'with_rsvg%': 'false', - 'variables': { # Nest jpeg_root to evaluate it before with_jpeg - 'jpeg_root%': ' switch (arch) { + .aarch64 => return "../npm/darwin-arm64/canvas.node", + .x86_64 => return "../npm/darwin-x64/canvas.node", + else => {}, + }, + .linux => switch (arch) { + .arm => switch (abi) { + .gnueabihf => return "../npm/linux-arm-gnueabihf/canvas.node", + else => {}, + }, + .x86 => switch (abi) { + .gnu => return "../npm/linux-x86-gnu/canvas.node", + .musl => return "../npm/linux-x86-musl/canvas.node", + else => {}, + }, + .aarch64 => switch (abi) { + .gnu => return "../npm/linux-arm64-gnu/canvas.node", + .musl => return "../npm/linux-arm64-musl/canvas.node", + else => {}, + }, + .riscv64 => switch (abi) { + .gnu => return "../npm/linux-riscv64-gnu/canvas.node", + else => {}, + }, + .x86_64 => switch (abi) { + .gnu => return "../npm/linux-x64-gnu/canvas.node", + .musl => return "../npm/linux-x64-musl/canvas.node", + else => {}, + }, + else => {}, + }, + .windows => switch (arch) { + .aarch64 => return "../npm/win32-arm64/canvas.node", + .x86_64 => return "../npm/win32-x64/canvas.node", + .x86 => return "../npm/win32-x86/canvas.node", + else => {}, + }, + else => {}, + } + + return "../bin/canvas.node"; +} + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const cairo = b.dependency("cairo", .{ + .target = target, + .optimize = optimize, + }).artifact("cairo"); + + const libjpeg_turbo = b.dependency("libjpeg_turbo", .{ + .target = target, + .optimize = optimize, + .pie = true, + }).artifact("libjpeg_turbo"); + + const giflib = b.dependency("giflib", .{ + .target = target, + .optimize = optimize, + }).artifact("giflib"); + + const libpng = b.dependency("libpng", .{ + .target = target, + .optimize = optimize, + }).artifact("png"); + + const sheenbidi = b.dependency("sheenbidi", .{ + .target = target, + .optimize = optimize, + }).artifact("sheenbidi"); + + const zg = b.dependency("zg", .{ + .target = target, + .optimize = optimize, + }); + + const harfbuzz = b.dependency("harfbuzz", .{ + .target = target, + .optimize = optimize, + }).artifact("harfbuzz"); + + const lunasvg = b.dependency("lunasvg", .{ + .target = target, + .optimize = optimize, + }).artifact("lunasvg"); + + const unicode = b.addLibrary(.{ + .name = "unicode", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/unicode.zig"), + .link_libc = true, + .pic = true, + }) + }); + + unicode.root_module.addImport("Scripts", zg.module("Scripts")); + unicode.root_module.addImport("Graphemes", zg.module("Graphemes")); + + const canvas = b.addLibrary(.{ + .name = "canvas", + .linkage = .dynamic, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .pic = true, + .link_libc = true, + .link_libcpp = true, + }) + }); + + const node_api = b.dependency("node_api", .{ + .target = target, + .optimize = optimize, + }).artifact("node_api"); + + canvas.root_module.addCSourceFiles(.{ + .files = &.{ + "src/bmp/BMPParser.cc", + "src/Canvas.cc", + "src/CanvasGradient.cc", + "src/CanvasPattern.cc", + "src/CanvasRenderingContext2d.cc", + "src/closure.cc", + "src/color.cc", + "src/Image.cc", + "src/ImageData.cc", + "src/init.cc", + "src/itemize.cc", + "src/FontManager.cc", + switch (target.result.os.tag) { + .windows => "src/FontManagerWindows.cc", + .macos => "src/FontManagerMacos.cc", + else => "src/FontManagerLinux.cc", + }, + "src/FontFace.cc", + "src/FontFaceSet.cc", + "src/FontParser.cc", + "src/FontLayout.cc" + } , + .flags = &.{ + "-DNAPI_DISABLE_CPP_EXCEPTIONS", + "-DNODE_ADDON_API_ENABLE_MAYBE", + "-D_USE_MATH_DEFINES", + "-std=c++20", + // dlclose gets called on the addon + // https://maskray.me/blog/2024-03-17-c++-exit-time-destructors + "-fno-c++-static-destructors", + if (target.result.os.tag == .windows) "-DCAIRO_WIN32_STATIC_BUILD" else "", + "-DLUNASVG_BUILD_STATIC", + } + }); + + canvas.linker_allow_shlib_undefined = true; + + if (optimize != .Debug) { + canvas.root_module.strip = true; + } + + canvas.root_module.addObject(node_api); + canvas.root_module.linkLibrary(cairo); + canvas.root_module.linkLibrary(libjpeg_turbo); + canvas.root_module.linkLibrary(libpng); + canvas.root_module.linkLibrary(giflib); + canvas.root_module.linkLibrary(sheenbidi); + canvas.root_module.linkLibrary(unicode); + canvas.root_module.linkLibrary(harfbuzz); + canvas.root_module.linkLibrary(lunasvg); + + // some deps, especially lunasvg, compile with -ffunction-sections and + // -fdata-sections so we don't have to distribute unused parts of libraries + canvas.link_gc_sections = true; + + if (target.result.os.tag == .windows) { + canvas.root_module.linkSystemLibrary("dwrite", .{}); + } + + if (target.result.os.tag == .macos) { + // Stole this method from + // https://github.com/ghostty-org/ghostty/commit/c0722b3652e5e207f34d220f64a03d9d53e93ad0 + + // The active SDK we want to use + const sdk = "MacOSX15.sdk"; + + // Get the path to our active Xcode installation. If this fails then + // the zig build will fail. + const path = std.mem.trim( + u8, + b.run(&.{ "xcode-select", "--print-path" }), + " \r\n", + ); + + canvas.root_module.addSystemFrameworkPath(.{ + .cwd_relative = b.pathJoin(&.{ + path, + "Platforms/MacOSX.platform/Developer/SDKs/" ++ sdk ++ "/System/Library/Frameworks", + }), + }); + canvas.root_module.addSystemIncludePath(.{ + .cwd_relative = b.pathJoin(&.{ + path, + "Platforms/MacOSX.platform/Developer/SDKs/" ++ sdk ++ "/usr/include", + }), + }); + canvas.root_module.addLibraryPath(.{ + .cwd_relative = b.pathJoin(&.{ + path, + "Platforms/MacOSX.platform/Developer/SDKs/" ++ sdk ++ "/usr/lib", + }), + }); + } + + const move = b.addInstallFile(canvas.getEmittedBin(), outputPath(target)); + move.step.dependOn(&canvas.step); + b.getInstallStep().dependOn(&move.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 000000000..7e2c32a00 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,36 @@ +.{ + .name = .canvas, + .fingerprint = 0xa59f6c18a5521654, + .version = "0.0.0", + .paths = .{""}, + .minimum_zig_version = "0.16.0", + // Sub-packages only specify dependencies for upstream C/C++ code. + // Everything else goes here so it's easier to share dependencies and ensure + // we don't build duplicate versions. Generally build.zig[.zon] files should + // be vendored so we can simplify them and have control over upgrades and + // things like -fPIC. Exceptions should (and are) justified below + .dependencies = .{ + // Simple and general enough to use the canonical URL + .node_api = .{ + .url = "https://github.com/chearon/node-api-zig/archive/37d992b2ff53608e301ce2d5bd76ec31a1a247d9.tar.gz", + .hash = "node_api-0.0.0-D68afJgfAABNKwoA-8I7d4v9fJUwM89W-yOHKMSNU7Em", + }, + // Too complicated to maintain here (large build.zig) + .libjpeg_turbo = .{ + .url = "https://github.com/chearon/libjpeg-turbo/archive/1222499df19186b61915ef6bf1b3bda15003584b.tar.gz", + .hash = "libjpeg_turbo-3.1.1-1-iiYWsnWCSQBHXuzUprRraTbBN-HJfN2dOX6BuZk6iKUi", + }, + // Zig dependencies don't need as much tweaking + .zg = .{ + .url = "https://codeberg.org/atman/zg/archive/ce364b17d60b28fda1af24c6575b93c47afe043e.tar.gz", + .hash = "zg-0.16.2-oGqU3HqftgI6rwFS90ypwfjgToH8Pz829OKuFURlm773", + }, + .giflib = .{ .path = "./pkg/giflib" }, + .libpng = .{ .path = "./pkg/libpng" }, + .cairo = .{ .path = "./pkg/cairo" }, + .sheenbidi = .{ .path = "./pkg/sheenbidi" }, + .harfbuzz = .{ .path = "./pkg/harfbuzz" }, + .freetype = .{ .path = "./pkg/freetype" }, + .lunasvg = .{ .path = "./pkg/lunasvg" } + }, +} diff --git a/examples/font.js b/examples/font.js index d2a37b825..a277ccf90 100644 --- a/examples/font.js +++ b/examples/font.js @@ -1,6 +1,6 @@ const fs = require('fs') const path = require('path') -const Canvas = require('..') +const { createCanvas, FontFace, fonts } = require('..') function fontFile (name) { return path.join(__dirname, '/pfennigFont/', name) @@ -10,12 +10,12 @@ function fontFile (name) { // `registerFont`. When you set `ctx.font`, refer to the styles and the family // name as it is embedded in the TTF. If you aren't sure, open the font in // FontForge and visit Element -> Font Information and copy the Family Name -Canvas.registerFont(fontFile('Pfennig.ttf'), { family: 'pfennigFont' }) -Canvas.registerFont(fontFile('PfennigBold.ttf'), { family: 'pfennigFont', weight: 'bold' }) -Canvas.registerFont(fontFile('PfennigItalic.ttf'), { family: 'pfennigFont', style: 'italic' }) -Canvas.registerFont(fontFile('PfennigBoldItalic.ttf'), { family: 'pfennigFont', weight: 'bold', style: 'italic' }) +fonts.add(new FontFace('pfennigFont', fontFile('Pfennig.ttf'))) +fonts.add(new FontFace('pfennigFont', fontFile('PfennigBold.ttf'), { weight: 'bold' })) +fonts.add(new FontFace('pfennigFont', fontFile('PfennigItalic.ttf'), { style: 'italic' })) +fonts.add(new FontFace('pfennigFont', fontFile('PfennigBoldItalic.ttf'), { weight: 'bold', style: 'italic' })) -const canvas = Canvas.createCanvas(320, 320) +const canvas = createCanvas(320, 320) const ctx = canvas.getContext('2d') ctx.font = 'normal normal 50px Helvetica' diff --git a/examples/images/badge.svg b/examples/images/badge.svg new file mode 100644 index 000000000..ee4a3b340 --- /dev/null +++ b/examples/images/badge.svg @@ -0,0 +1,5 @@ + + + + diff --git a/examples/worker-threads.js b/examples/worker-threads.js new file mode 100644 index 000000000..bc68a252f --- /dev/null +++ b/examples/worker-threads.js @@ -0,0 +1,65 @@ +const { Worker, isMainThread, workerData, parentPort } = require('worker_threads') +const fs = require('fs') +const path = require('path') + +const ARIMO = path.join(__dirname, '../test/fixtures/Arimo-Regular.ttf') +const PFENNIG = path.join(__dirname, 'pfennigFont/Pfennig.ttf') +const BADGE = path.join(__dirname, 'images/badge.svg') + +const WORKERS = [ + { index: 0, color: '#1a1a2e', fonts: ['arimo'], useFont: 'Arimo', svg: false }, + { index: 1, color: '#16213e', fonts: ['arimo'], useFont: 'Arimo, Pfennig', svg: false }, + { index: 2, color: '#0f3460', fonts: ['arimo', 'pfennig'], useFont: 'Arimo', svg: true }, + { index: 3, color: '#533483', fonts: ['arimo', 'pfennig'], useFont: 'Pfennig', svg: true } +] + +if (isMainThread) { + let done = 0 + + for (const config of WORKERS) { + const worker = new Worker(__filename, { workerData: config }) + + worker.on('message', ({ index, buffer }) => { + const file = path.join(__dirname, `worker-${index}.png`) + fs.writeFileSync(file, buffer) + console.log(`Wrote ${file}`) + if (++done === WORKERS.length) console.log('All workers done.') + }) + + worker.on('error', (err) => { + console.error(`Worker ${config.index} error:`, err) + }) + } +} else { + const { createCanvas, FontFace, fonts, loadImage } = require('..') + const { index, color, fonts: fontNames, useFont, svg } = workerData + + if (fontNames.includes('arimo')) fonts.add(new FontFace('Arimo', ARIMO)) + if (fontNames.includes('pfennig')) fonts.add(new FontFace('Pfennig', PFENNIG)) + + const render = async () => { + const canvas = createCanvas(200, 200) + const ctx = canvas.getContext('2d') + + ctx.fillStyle = color + ctx.fillRect(0, 0, 200, 200) + + if (svg) { + const badge = await loadImage(BADGE) + ctx.drawImage(badge, 10, 10, 80, 80) + } + + ctx.fillStyle = 'white' + ctx.font = `bold 26px ${useFont}` + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.fillText(`Worker ${index}`, 100, 110) + + ctx.font = `13px ${useFont}` + ctx.fillText(`${useFont} / [${fontNames.join(', ')}]`, 100, 140) + + return canvas.toBuffer('image/png') + } + + render().then(buffer => parentPort.postMessage({ index, buffer })) +} diff --git a/index.d.ts b/index.d.ts index 27ab0c341..886ec2dfd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -378,19 +378,34 @@ export function createImageData(width: number, height: number): ImageData */ export function loadImage(src: string|Buffer, options?: any): Promise -/** - * Registers a font that is not installed as a system font. This must be used - * before creating Canvas instances. - * @param path Path to local font file. - * @param fontFace Description of the font face, corresponding to CSS properties - * used in `@font-face` rules. - */ -export function registerFont(path: string, fontFace: {family: string, weight?: string, style?: string}): void +interface FontFaceDescriptors { + weight?: string | number; + style?: string; +} -/** - * Unloads all fonts - */ -export function deregisterAllFonts(): void; +export class FontFace { + constructor( + family: string, + url: string | ArrayBuffer | Uint8Array, + descriptors?: FontFaceDescriptors + ); + + family: string; + style: string; + weight: string; + status: 'loaded' | 'unloaded' | 'error'; +} + +declare class FontFaceSet { + add(face: FontFace): void; + has(face: FontFace): boolean; + clear(): void; + delete(face: FontFace): boolean; + [Symbol.iterator](): Iterator; + size: number; +} + +export const fonts: FontFaceSet; /** This class must not be constructed directly; use `canvas.createPNGStream()`. */ export class PNGStream extends Readable {} diff --git a/index.js b/index.js index adde4da12..d75f784bb 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,6 @@ const CanvasRenderingContext2D = require('./lib/context2d') const CanvasPattern = require('./lib/pattern') const packageJson = require('./package.json') const bindings = require('./lib/bindings') -const fs = require('fs') const PNGStream = require('./lib/pngstream') const PDFStream = require('./lib/pdfstream') const JPEGStream = require('./lib/jpegstream') @@ -36,31 +35,12 @@ function loadImage (src) { }) } -/** - * Resolve paths for registerFont. Must be called *before* creating a Canvas - * instance. - * @param src {string} Path to font file. - * @param fontFace {{family: string, weight?: string, style?: string}} Object - * specifying font information. `weight` and `style` default to `"normal"`. - */ -function registerFont (src, fontFace) { - // TODO this doesn't need to be on Canvas; it should just be a static method - // of `bindings`. - return Canvas._registerFont(fs.realpathSync(src), fontFace) -} - -/** - * Unload all fonts from pango to free up memory - */ -function deregisterAllFonts () { - return Canvas._deregisterAllFonts() -} - exports.Canvas = Canvas exports.Context2d = CanvasRenderingContext2D // Legacy/compat export exports.CanvasRenderingContext2D = CanvasRenderingContext2D exports.CanvasGradient = bindings.CanvasGradient exports.CanvasPattern = CanvasPattern +exports.FontFace = bindings.FontFace exports.Image = Image exports.ImageData = bindings.ImageData exports.PNGStream = PNGStream @@ -69,8 +49,7 @@ exports.JPEGStream = JPEGStream exports.DOMMatrix = DOMMatrix exports.DOMPoint = DOMPoint -exports.registerFont = registerFont -exports.deregisterAllFonts = deregisterAllFonts +exports.fonts = bindings.fonts exports.createCanvas = createCanvas exports.createImageData = createImageData diff --git a/index.test-d.ts b/index.test-d.ts index f898f2d58..35b5afa96 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,10 +1,22 @@ import { expectAssignable, expectType } from 'tsd' -import * as path from 'path' import { Readable } from 'stream' import * as Canvas from './index' -Canvas.registerFont(path.join(__dirname, '../pfennigFont/Pfennig.ttf'), {family: 'pfennigFont'}) +const font = new Canvas.FontFace('pfennigFont', '../pfennigFont/Pfennig.ttf', { + weight: 'normal', + style: 'normal' +}); + +Canvas.fonts.add(font) +font.status; +font.family = 'pfennig'; +font.style = 'italic'; +font.weight = 'bold'; +Canvas.fonts.delete(font); +Canvas.fonts.clear(); +Canvas.fonts.size; +for (const font of Canvas.fonts) font.status; Canvas.createCanvas(5, 10) Canvas.createCanvas(200, 200, 'pdf') @@ -49,5 +61,3 @@ expectType(id2) ctx.putImageData(id2, 0, 0) ctx.drawImage(canv, 0, 0) - -Canvas.deregisterAllFonts() diff --git a/lib/bindings.js b/lib/bindings.js index 40cef3c69..a17a91924 100644 --- a/lib/bindings.js +++ b/lib/bindings.js @@ -1,6 +1,92 @@ 'use strict' -const bindings = require('../build/Release/canvas.node') +const fs = require('fs'); +// https://github.com/yarnpkg/berry/blob/fe0418084777ccd7a1524b3315102da9d5ce6a9f/packages/yarnpkg-core/sources/nodeUtils.ts#L28 +function getLibc() { + // As of 2025, linux is the only possible process.platform value that does not + // imply the libc for Node's purposes. Technically mingw32 (a way to build and + // run software using glibc on Windows) exists and even has a node.js port, + // but no one in the broader node.js ecosystem seems to care about it. There + // have been issues in the past running getReport() on Windows. + if (process.platform !== 'linux') return null + + let header + try { + header = fs.readFileSync('/usr/bin/ldd') + } catch {} + + // Since the getReport can be prohibitely expensive (it also queries DNS + // which, if misconfigured, can take a long time to timeout), we first check + // if the ldd binary is glibc or musl, and only then run the getReport() if we + // can't determine the libc variant. + if (typeof header !== 'undefined') { + if (header && (header.includes('GLIBC') || header.includes('GNU libc') || header.includes('GNU C Library'))) + return 'glibc' + if (header && header.includes('musl')) { + return 'musl' + } + } + + const report = process.report?.getReport() ?? {} + const sharedObjects = report.sharedObjects ?? [] + + // Matches the first group if libc, second group if musl + const libcRegExp = /\/(?:(ld-linux-|[^/]+-linux-gnu\/)|(libc.musl-|ld-musl-))/ + + for (const entry of sharedObjects) { + const match = entry.match(libcRegExp) + + if (match) { + if (match[1]) return 'glibc' + if (match[2]) return 'musl' + throw new Error('Assertion failed: Expected the libc variant to have been detected') + } + } +} + +// mirror changes here with build.zig +function requireAddon() { + switch (process.platform) { + case 'darwin': + switch (process.arch) { + case 'arm64': return require('canvas-darwin-arm64'); + case 'x64': return require('canvas-darwin-x64'); + } + break; + case 'linux': + switch (process.arch) { + case 'arm': return require('canvas-linux-arm-gnueabihf'); + case 'ia32': + switch (getLibc()) { + case 'musl': return require('canvas-linux-x86-musl'); + default: return require('canvas-linux-x86-gnu'); + } + case 'arm64': + switch (getLibc()) { + case 'musl': return require('canvas-linux-arm64-musl'); + default: return require('canvas-linux-arm64-gnu'); + } + case 'riscv64': return require('canvas-linux-riscv64-gnu'); + case 'x64': + switch (getLibc()) { + case 'musl': return require('canvas-linux-x64-musl'); + default: return require('canvas-linux-x64-gnu'); + } + } + break; + case 'win32': + switch (process.arch) { + case 'arm64': return require('canvas-win32-arm64'); + case 'x64': return require('canvas-win32-x64'); + case 'ia32': return require('canvas-win32-x86'); + } + break; + } + + return require('../bin/canvas.node'); +} + +const bindings = requireAddon() module.exports = bindings @@ -41,3 +127,8 @@ Object.defineProperty(bindings.CanvasRenderingContext2d.prototype, Symbol.toStri value: 'CanvasRenderingContext2d', configurable: true }) + +Object.defineProperty(bindings.FontFace.prototype, Symbol.toStringTag, { + value: 'FontFace', + configurable: true +}) diff --git a/lib/jpegstream.js b/lib/jpegstream.js index 701d2f870..8f98a9db7 100644 --- a/lib/jpegstream.js +++ b/lib/jpegstream.js @@ -13,10 +13,6 @@ class JPEGStream extends Readable { constructor (canvas, options) { super() - if (canvas.streamJPEGSync === undefined) { - throw new Error('node-canvas was built without JPEG support.') - } - this.options = options this.canvas = canvas } diff --git a/npm/darwin-arm64/package.json b/npm/darwin-arm64/package.json new file mode 100644 index 000000000..8b5ed3c5a --- /dev/null +++ b/npm/darwin-arm64/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-darwin-arm64", + "version": "3.2.3", + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/darwin-x64/package.json b/npm/darwin-x64/package.json new file mode 100644 index 000000000..31f144867 --- /dev/null +++ b/npm/darwin-x64/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-darwin-x64", + "version": "3.2.3", + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/linux-arm-gnueabihf/package.json b/npm/linux-arm-gnueabihf/package.json new file mode 100644 index 000000000..526525519 --- /dev/null +++ b/npm/linux-arm-gnueabihf/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-linux-arm-gnueabihf", + "version": "3.2.3", + "os": [ + "linux" + ], + "cpu": [ + "arm" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/linux-arm64-gnu/package.json b/npm/linux-arm64-gnu/package.json new file mode 100644 index 000000000..514bfdd4b --- /dev/null +++ b/npm/linux-arm64-gnu/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-linux-arm64-gnu", + "version": "3.2.3", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/linux-arm64-musl/package.json b/npm/linux-arm64-musl/package.json new file mode 100644 index 000000000..2e8279bf9 --- /dev/null +++ b/npm/linux-arm64-musl/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-linux-arm64-musl", + "version": "3.2.3", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/linux-riscv64-gnu/package.json b/npm/linux-riscv64-gnu/package.json new file mode 100644 index 000000000..797035b15 --- /dev/null +++ b/npm/linux-riscv64-gnu/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-linux-riscv64-gnu", + "version": "3.2.3", + "os": [ + "linux" + ], + "cpu": [ + "riscv64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/linux-x64-gnu/package.json b/npm/linux-x64-gnu/package.json new file mode 100644 index 000000000..50a370b52 --- /dev/null +++ b/npm/linux-x64-gnu/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-linux-x64-gnu", + "version": "3.2.3", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/linux-x64-musl/package.json b/npm/linux-x64-musl/package.json new file mode 100644 index 000000000..b07b165d7 --- /dev/null +++ b/npm/linux-x64-musl/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-linux-x64-musl", + "version": "3.2.3", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/linux-x86-gnu/package.json b/npm/linux-x86-gnu/package.json new file mode 100644 index 000000000..ed9d31715 --- /dev/null +++ b/npm/linux-x86-gnu/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-linux-x86-gnu", + "version": "3.2.3", + "os": [ + "linux" + ], + "cpu": [ + "ia32" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/linux-x86-musl/package.json b/npm/linux-x86-musl/package.json new file mode 100644 index 000000000..8b5ba701b --- /dev/null +++ b/npm/linux-x86-musl/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-linux-x86-musl", + "version": "3.2.3", + "os": [ + "linux" + ], + "cpu": [ + "ia32" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/win32-arm64/package.json b/npm/win32-arm64/package.json new file mode 100644 index 000000000..de991a328 --- /dev/null +++ b/npm/win32-arm64/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-win32-arm64", + "version": "3.2.3", + "os": [ + "win32" + ], + "cpu": [ + "arm64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/win32-x64/package.json b/npm/win32-x64/package.json new file mode 100644 index 000000000..ea7e8978f --- /dev/null +++ b/npm/win32-x64/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-win32-x64", + "version": "3.2.3", + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/npm/win32-x86/package.json b/npm/win32-x86/package.json new file mode 100644 index 000000000..9344d8731 --- /dev/null +++ b/npm/win32-x86/package.json @@ -0,0 +1,15 @@ +{ + "name": "canvas-win32-x86", + "version": "3.2.3", + "os": [ + "win32" + ], + "cpu": [ + "ia32" + ], + "main": "canvas.node", + "files": [ + "canvas.node" + ], + "description": "Canvas graphics API backed by Cairo" +} diff --git a/package-lock.json b/package-lock.json index bb1d0be29..367c65764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,12 +7,10 @@ "": { "name": "canvas", "version": "3.2.3", - "hasInstallScript": true, "license": "MIT", - "dependencies": { - "node-addon-api": "^8.9.0", - "prebuild-install": "^7.1.3" - }, + "workspaces": [ + "./npm/*" + ], "devDependencies": { "@types/node": "^10.12.18", "assert-rejects": "^1.0.0", @@ -26,16 +24,28 @@ }, "engines": { "node": "^18.12.0 || >= 20.9.0" + }, + "optionalDependencies": { + "canvas-darwin-arm64": "3.2.3", + "canvas-darwin-x64": "3.2.3", + "canvas-linux-arm-gnueabihf": "3.2.3", + "canvas-linux-arm64-gnu": "3.2.3", + "canvas-linux-arm64-musl": "3.2.3", + "canvas-linux-riscv64-gnu": "3.2.3", + "canvas-linux-x64-gnu": "3.2.3", + "canvas-linux-x64-musl": "3.2.3", + "canvas-win32-arm64": "3.2.3", + "canvas-win32-x64": "3.2.3" } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -51,9 +61,9 @@ "license": "MIT" }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -78,91 +88,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -226,9 +151,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, "license": "MIT" }, @@ -254,9 +179,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -326,9 +251,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -363,26 +288,32 @@ } }, "node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/argparse": { @@ -449,6 +380,25 @@ "node": ">=8" } }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -569,6 +519,16 @@ "node": ">=0.10.0" } }, + "node_modules/babel-code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/babel-code-frame/node_modules/strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -599,37 +559,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/body-parser": { "version": "1.20.5", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", @@ -655,46 +584,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -717,30 +614,6 @@ "dev": true, "license": "ISC" }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -752,15 +625,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -852,19 +725,86 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/canvas-darwin-arm64": { + "resolved": "npm/darwin-arm64", + "link": true + }, + "node_modules/canvas-darwin-x64": { + "resolved": "npm/darwin-x64", + "link": true + }, + "node_modules/canvas-linux-arm-gnueabihf": { + "resolved": "npm/linux-arm-gnueabihf", + "link": true + }, + "node_modules/canvas-linux-arm64-gnu": { + "resolved": "npm/linux-arm64-gnu", + "link": true + }, + "node_modules/canvas-linux-arm64-musl": { + "resolved": "npm/linux-arm64-musl", + "link": true + }, + "node_modules/canvas-linux-riscv64-gnu": { + "resolved": "npm/linux-riscv64-gnu", + "link": true + }, + "node_modules/canvas-linux-x64-gnu": { + "resolved": "npm/linux-x64-gnu", + "link": true + }, + "node_modules/canvas-linux-x64-musl": { + "resolved": "npm/linux-x64-musl", + "link": true + }, + "node_modules/canvas-linux-x86-gnu": { + "resolved": "npm/linux-x86-gnu", + "link": true + }, + "node_modules/canvas-linux-x86-musl": { + "resolved": "npm/linux-x86-musl", + "link": true + }, + "node_modules/canvas-win32-arm64": { + "resolved": "npm/win32-arm64", + "link": true + }, + "node_modules/canvas-win32-x64": { + "resolved": "npm/win32-x64", + "link": true + }, + "node_modules/canvas-win32-x86": { + "resolved": "npm/win32-x86", + "link": true + }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/chardet": { @@ -890,12 +830,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", @@ -949,15 +883,12 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", @@ -987,20 +918,41 @@ "node": ">=8" } }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, @@ -1045,9 +997,9 @@ } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -1055,37 +1007,25 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=4.8" - } - }, - "node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "node": ">= 8" } }, "node_modules/data-view-buffer": { @@ -1199,30 +1139,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1281,6 +1197,39 @@ "uniq": "^1.0.1" } }, + "node_modules/deglob/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/deglob/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/deglob/node_modules/ignore": { "version": "3.3.10", "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", @@ -1288,6 +1237,19 @@ "dev": true, "license": "MIT" }, + "node_modules/deglob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1309,15 +1271,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/diff": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", @@ -1404,9 +1357,9 @@ "license": "MIT" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, @@ -1420,19 +1373,10 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1440,9 +1384,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -1508,6 +1452,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1529,9 +1492,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -1557,16 +1520,32 @@ "node": ">= 0.4" } }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", "dev": true, "license": "MIT", "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -1593,13 +1572,16 @@ "license": "MIT" }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { @@ -1730,166 +1712,111 @@ "node": ">=8" } }, - "node_modules/eslint-formatter-pretty/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/eslint-formatter-pretty/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-formatter-pretty/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint-formatter-pretty/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint-formatter-pretty/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-regex": "^5.0.1" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-formatter-pretty/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, - "node_modules/eslint-formatter-pretty/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-formatter-pretty/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-formatter-pretty/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-formatter-pretty/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "ms": "^2.1.1" } }, - "node_modules/eslint-formatter-pretty/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/eslint-import-resolver-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/eslint-formatter-pretty/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-formatter-pretty/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-import-resolver-node/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", "dev": true, "license": "MIT", "dependencies": { @@ -1963,6 +1890,17 @@ "eslint": "2.x - 5.x" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", @@ -1983,6 +1921,19 @@ "dev": true, "license": "MIT" }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-node": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz", @@ -2004,14 +1955,28 @@ "eslint": ">=4.19.1" } }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/eslint-plugin-node/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-node/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver" + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, "node_modules/eslint-plugin-promise": { @@ -2112,6 +2077,29 @@ "node": ">=4" } }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/eslint/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2122,6 +2110,66 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, "node_modules/eslint/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -2132,10 +2180,52 @@ "ms": "^2.1.1" } }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/eslint/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -2146,6 +2236,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2153,14 +2256,86 @@ "dev": true, "license": "MIT" }, - "node_modules/eslint/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/eslint/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, "bin": { - "semver": "bin/semver" + "which": "bin/which" } }, "node_modules/espree": { @@ -2193,9 +2368,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2268,15 +2443,6 @@ "node": ">= 0.6" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/express": { "version": "4.22.2", "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", @@ -2400,6 +2566,16 @@ "node": ">=4" } }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", @@ -2428,18 +2604,18 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -2454,16 +2630,20 @@ "license": "MIT" }, "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^2.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat": { @@ -2485,121 +2665,44 @@ "dependencies": { "circular-json": "^0.3.1", "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/foreground-child/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", + "rimraf": "~2.6.2", + "write": "^0.2.1" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/forwarded": { @@ -2622,12 +2725,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2646,18 +2743,21 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -2683,6 +2783,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2760,29 +2870,26 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { @@ -2933,13 +3040,13 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { @@ -3001,9 +3108,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -3031,20 +3138,24 @@ "license": "ISC" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { @@ -3060,26 +3171,6 @@ "node": ">=0.10.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -3097,66 +3188,186 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.1.0", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^5.5.2", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/inquirer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/inquirer/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "license": "MIT", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "node_modules/inquirer/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", "dependencies": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=4" } }, "node_modules/internal-slot": { @@ -3286,13 +3497,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -3336,6 +3547,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3363,24 +3590,25 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" }, @@ -3698,82 +3926,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -3792,9 +3944,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, "funding": [ { @@ -3904,17 +4056,19 @@ } }, "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -3941,82 +4095,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4026,23 +4104,17 @@ "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "bin": { + "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -4143,6 +4215,19 @@ "node": ">=8" } }, + "node_modules/meow/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/meow/node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -4188,16 +4273,6 @@ "node": ">=8" } }, - "node_modules/meow/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/meow/node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -4217,16 +4292,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/meow/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/meow/node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -4311,6 +4376,29 @@ "node": ">=8" } }, + "node_modules/meow/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -4381,258 +4469,37 @@ "node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/mocha": { - "version": "11.7.6", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", - "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", - "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", - "ms": "^2.1.3", - "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^9.2.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "mime-db": "1.52.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 0.6" } }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/mocha/node_modules/minimatch": { + "node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", @@ -4648,94 +4515,116 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/mocha/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "minimist": "^1.2.6" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/mocha": { + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/mocha/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -4750,12 +4639,6 @@ "dev": true, "license": "ISC" }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4780,25 +4663,33 @@ "dev": true, "license": "MIT" }, - "node_modules/node-abi": { - "version": "3.85.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", - "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-addon-api": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.0.tgz", - "integrity": "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, "node_modules/normalize-package-data": { @@ -4814,16 +4705,6 @@ "validate-npm-package-license": "^3.0.1" } }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4878,6 +4759,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -4895,6 +4792,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4960,39 +4858,45 @@ } }, "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^1.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^1.1.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/package-json-from-dist": { @@ -5026,13 +4930,13 @@ } }, "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/path-is-absolute": { @@ -5053,13 +4957,13 @@ "license": "(WTFPL OR MIT)" }, "node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/path-parse": { @@ -5086,17 +4990,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "dev": true, "license": "MIT" }, @@ -5170,6 +5067,19 @@ "node": ">=4" } }, + "node_modules/pkg-conf/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/pkg-conf/node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -5186,6 +5096,56 @@ "node": ">=4" } }, + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/pkg-conf/node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -5200,6 +5160,16 @@ "node": ">=4" } }, + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/pkg-conf/node_modules/pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -5271,32 +5241,6 @@ "node": ">= 0.4" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -5377,16 +5321,6 @@ "node": ">= 0.10" } }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5398,13 +5332,14 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -5480,52 +5415,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5562,18 +5451,77 @@ "node": ">=4" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" }, "engines": { - "node": ">= 6" + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, "node_modules/readdirp": { @@ -5683,13 +5631,14 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5727,6 +5676,13 @@ "node": ">=4" } }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -5752,11 +5708,22 @@ "rimraf": "bin.js" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -5774,6 +5741,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -5822,15 +5802,15 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -5845,6 +5825,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -5904,52 +5885,40 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, "license": "ISC", "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "semver": "bin/semver" } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5968,16 +5937,16 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -6040,38 +6009,38 @@ "license": "ISC" }, "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -6083,14 +6052,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -6139,55 +6108,16 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC" - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/slash": { @@ -6213,6 +6143,16 @@ "node": ">=4" } }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -6243,9 +6183,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, "license": "CC0-1.0" }, @@ -6294,9 +6234,9 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -6317,27 +6257,22 @@ "node": ">= 0.4" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -6366,15 +6301,12 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", @@ -6390,19 +6322,20 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6412,16 +6345,16 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.2" }, "engines": { "node": ">= 0.4" @@ -6449,16 +6382,19 @@ } }, "node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi-cjs": { @@ -6509,25 +6445,32 @@ } }, "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-hyperlinks": { @@ -6544,16 +6487,6 @@ "node": ">=8" } }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/supports-hyperlinks/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6608,32 +6541,129 @@ "node": ">=4.0.0" } }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "node_modules/table/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/table/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/table/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/table/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/table/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "license": "MIT", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=4" } }, "node_modules/text-table": { @@ -6774,16 +6804,6 @@ "node": ">=8" } }, - "node_modules/tsd/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/tsd/node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -6803,16 +6823,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tsd/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/tsd/node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -6867,18 +6877,6 @@ "node": ">=8" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -6977,18 +6975,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -7057,12 +7055,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -7095,16 +7087,19 @@ } }, "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, "bin": { - "which": "bin/which" + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, "node_modules/which-boxed-primitive": { @@ -7175,14 +7170,14 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", @@ -7214,18 +7209,18 @@ "license": "Apache-2.0" }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -7260,52 +7255,13 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -7334,94 +7290,24 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/write": { @@ -7465,9 +7351,9 @@ "license": "ISC" }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -7484,13 +7370,13 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-unparser": { @@ -7555,15 +7441,12 @@ "node": ">=8" } }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", @@ -7593,16 +7476,6 @@ "node": ">=8" } }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -7615,6 +7488,136 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "npm/darwin-arm64": { + "name": "canvas-darwin-arm64", + "version": "3.2.3", + "cpu": [ + "arm64" + ], + "os": [ + "darwin" + ] + }, + "npm/darwin-x64": { + "name": "canvas-darwin-x64", + "version": "3.2.3", + "cpu": [ + "x64" + ], + "os": [ + "darwin" + ] + }, + "npm/linux-arm-gnueabihf": { + "name": "canvas-linux-arm-gnueabihf", + "version": "3.2.3", + "cpu": [ + "arm" + ], + "os": [ + "linux" + ] + }, + "npm/linux-arm64-gnu": { + "name": "canvas-linux-arm64-gnu", + "version": "3.2.3", + "cpu": [ + "arm64" + ], + "os": [ + "linux" + ] + }, + "npm/linux-arm64-musl": { + "name": "canvas-linux-arm64-musl", + "version": "3.2.3", + "cpu": [ + "arm64" + ], + "os": [ + "linux" + ] + }, + "npm/linux-riscv64-gnu": { + "name": "canvas-linux-riscv64-gnu", + "version": "3.2.3", + "cpu": [ + "riscv64" + ], + "os": [ + "linux" + ] + }, + "npm/linux-x64-gnu": { + "name": "canvas-linux-x64-gnu", + "version": "3.2.3", + "cpu": [ + "x64" + ], + "os": [ + "linux" + ] + }, + "npm/linux-x64-musl": { + "name": "canvas-linux-x64-musl", + "version": "3.2.3", + "cpu": [ + "x64" + ], + "os": [ + "linux" + ] + }, + "npm/linux-x86-gnu": { + "name": "canvas-linux-x86-gnu", + "version": "3.2.3", + "cpu": [ + "ia32" + ], + "os": [ + "linux" + ] + }, + "npm/linux-x86-musl": { + "name": "canvas-linux-x86-musl", + "version": "3.2.3", + "cpu": [ + "ia32" + ], + "os": [ + "linux" + ] + }, + "npm/win32-arm64": { + "name": "canvas-win32-arm64", + "version": "3.2.3", + "cpu": [ + "arm64" + ], + "os": [ + "win32" + ] + }, + "npm/win32-x64": { + "name": "canvas-win32-x64", + "version": "3.2.3", + "cpu": [ + "x64" + ], + "os": [ + "win32" + ] + }, + "npm/win32-x86": { + "name": "canvas-win32-x86", + "version": "3.2.3", + "cpu": [ + "ia32" + ], + "os": [ + "win32" + ] } } } diff --git a/package.json b/package.json index 44e98875f..d0cce2ae6 100644 --- a/package.json +++ b/package.json @@ -24,30 +24,30 @@ "homepage": "https://github.com/Automattic/node-canvas", "repository": "git://github.com/Automattic/node-canvas.git", "scripts": { - "prebenchmark": "node-gyp build", + "prebenchmark": "zig build", "benchmark": "node benchmarks/run.js", "lint": "standard examples/*.js test/server.js test/public/*.js benchmarks/run.js lib/context2d.js util/has_lib.js browser.js index.js", "test": "mocha test/*.test.js", - "pretest-server": "node-gyp build", + "pretest-server": "zig build", "test-server": "node test/server.js", "generate-wpt": "node ./test/wpt/generate.js", "test-wpt": "mocha test/wpt/generated/*.js", - "install": "prebuild-install -r napi || node-gyp rebuild", "tsd": "tsd" }, "files": [ - "binding.gyp", + "build.zig", + "build.zig.zon", "browser.js", "index.d.ts", "index.js", "lib/", + "pkg/", "src/", "util/" ], - "dependencies": { - "node-addon-api": "^8.9.0", - "prebuild-install": "^7.1.3" - }, + "workspaces": [ + "./npm/*" + ], "devDependencies": { "@types/node": "^10.12.18", "assert-rejects": "^1.0.0", @@ -59,13 +59,23 @@ "tsd": "^0.29.0", "typescript": "^4.2.2" }, + "optionalDependencies": { + "canvas-darwin-arm64": "3.2.3", + "canvas-darwin-x64": "3.2.3", + "canvas-linux-arm-gnueabihf": "3.2.3", + "canvas-linux-x86-musl": "3.2.3", + "canvas-linux-x86-gnu": "3.2.3", + "canvas-linux-arm64-musl": "3.2.3", + "canvas-linux-arm64-gnu": "3.2.3", + "canvas-linux-riscv64-gnu": "3.2.3", + "canvas-linux-x64-musl": "3.2.3", + "canvas-linux-x64-gnu": "3.2.3", + "canvas-win32-arm64": "3.2.3", + "canvas-win32-x64": "3.2.3", + "canvas-win32-x86": "3.2.3" + }, "engines": { "node": "^18.12.0 || >= 20.9.0" }, - "binary": { - "napi_versions": [ - 7 - ] - }, "license": "MIT" } diff --git a/pkg/cairo/build.zig b/pkg/cairo/build.zig new file mode 100644 index 000000000..8390ff2f7 --- /dev/null +++ b/pkg/cairo/build.zig @@ -0,0 +1,267 @@ +const std = @import("std"); +const sources = @import("sources.zig"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const cairo = b.dependency("cairo", .{}); + + const lib = b.addLibrary(.{ + .name = "cairo", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = true, + }), + }); + + lib.link_data_sections = true; + lib.link_function_sections = true; + lib.root_module.addIncludePath(cairo.path("src")); + + var cairo_sources = std.ArrayList([]const u8).empty; + defer cairo_sources.deinit(b.allocator); + + try cairo_sources.appendSlice(b.allocator, sources.cairo); + + var c_flags = std.ArrayList([]const u8).empty; + defer c_flags.deinit(b.allocator); + try c_flags.appendSlice(b.allocator, &.{ + "-Wmissing-declarations", + "-Werror-implicit-function-declaration", + "-Wpointer-arith", + "-Wwrite-strings", + "-Wsign-compare", + "-Wpacked", + "-Wswitch-enum", + "-Wmissing-format-attribute", + "-Wvolatile-register-var", + "-Wstrict-aliasing=2", + "-Winit-self", + // "-Wunsafe-loop-optimizations", + "-Wno-missing-field-initializers", + "-Wno-unused-parameter", + "-Wno-attributes", + "-Wno-long-long", + "-Winline", + "-Wno-unused-but-set-variable", + "-Wno-enum-conversion", + "-fno-strict-aliasing", + "-fno-common", + "-D_GNU_SOURCE", + "-DCAIRO_COMPILATION", + "-fno-sanitize=undefined", + }); + if (optimize != .Debug) { + try c_flags.append(b.allocator, "-Wp,-D_FORTIFY_SOURCE=2"); + } + + const config = b.addConfigHeader(.{}, .{ + .HAVE_STDINT_H = 1, + .HAVE_INTTYPES_H = 1, + .HAVE_FCNTL_H = 1, + .HAVE_UNISTD_H = 1, + .HAVE_TIME_H = 1, + .HAVE_LIBGEN_H = 1, + .HAVE_SIGNAL_H = 1, + .HAVE_SETJMP_H = 1, + .HAVE_SYS_STAT_H = 1, + .HAVE_UINT64_T = 1, + .HAVE_ALARM = 1, + .HAVE_GETLINE = 1, + .HAVE_RAISE = 1, + .HAVE_STRTOD_L = 1, + .HAVE_CLOCK_GETTIME = 1, + .HAVE_C11_ATOMIC_PRIMITIVES = 1, + // 2 means variant 2 (see meson-cc-tests/mkdir-variant-2.c) + // aka `int mkdir(const char *, mode_t)` + .HAVE_MKDIR = 2, + .SIZEOF_VOID_P = target.result.ptrBitWidth() / 8, + .SIZEOF_INT = target.result.cTypeByteSize(.int), + .SIZEOF_LONG = target.result.cTypeByteSize(.long), + .SIZEOF_LONG_LONG = target.result.cTypeByteSize(.longlong), + .SIZEOF_SIZE_T = target.result.ptrBitWidth() / 8, + }); + + // not checking the real source of truth here, but works in practice + if (target.result.ptrBitWidth() == 64) { + config.addValues(.{ + .HAVE___UINT128_T = 1, + }); + } + + if (target.result.os.tag == .windows) + config.addValues(.{ + .HAVE_INTSAFE_H = 1, + }) + else + config.addValues(.{ + .HAVE_SYS_SOCKET_H = 1, + .HAVE_POLL_H = 1, + .HAVE_SYS_POLL_H = 1, + .HAVE_SYS_UN_H = 1, + .HAVE_SCHED_H = 1, + .HAVE_SYS_MMAN_H = 1, + .HAVE_SYS_IOCTL_H = 1, + .HAVE_TERMIOS_H = 1, + .HAVE_DRAND48 = 1, + .HAVE_LINK = 1, + .HAVE_FORK = 1, + .HAVE_NEWLOCALE = 1, + .HAVE_STRNDUP = 1, + .HAVE_MMAP = 1, + .HAVE_CTIME_R = 1, + .HAVE_LOCALTIME_R = 1, + .HAVE_GMTIME_R = 1, + }); + + if (target.result.os.tag == .linux or target.result.cpu.arch.isWasm()) + config.addValues(.{ + .HAVE_BYTESWAP_H = 1, + .HAVE_FENV_H = 1, + .HAVE_SYS_WAIT_H = 1, + .HAVE_ALLOCA_H = 1, + .HAVE_FLOCKFILE = 1, + .HAVE_FUNLOCKFILE = 1, + .HAVE_SCHED_GETAFFINITY = 1, + }); + + if (target.result.os.tag.isDarwin()) + config.addValues(.{ .HAVE_XLOCALE_H = 1 }) + else + config.addValues(.{ .HAVE_IO_H = 1 }); + + if (target.result.os.tag.isDarwin() or target.result.os.tag == .linux) + config.addValues(.{ .HAVE_WAITPID = 1 }); + + if (target.result.cpu.arch.endian() == .big) + config.addValues(.{ + .WORDS_BIGENDIAN = 1, + .FLOAT_WORDS_BIGENDIAN = 1, + }); + + const feature_config = b.addConfigHeader(.{ .include_path = "cairo-features.h" }, .{}); + + lib.root_module.linkSystemLibrary("m", .{}); + + const zlib = b.dependency("zlib", .{ + .target = target, + .optimize = optimize, + }); + + lib.root_module.linkLibrary(zlib.artifact("z")); + + config.addValues(.{ + .HAVE_ZLIB = 1, + .CAIRO_HAS_INTERPRETER = 1, + .CAIRO_CAN_TEST_PS_SURFACE = 1, + }); + feature_config.addValues(.{ + .CAIRO_HAS_PS_SURFACE = 1, + .CAIRO_HAS_PDF_SURFACE = 1, + .CAIRO_HAS_SCRIPT_SURFACE = 1, + }); + + try cairo_sources.appendSlice(b.allocator, sources.script_surface); + const png = b.dependency("libpng", .{ + .target = target, + .optimize = optimize, + }); + + lib.root_module.linkLibrary(png.artifact("png")); + + config.addValues(.{ + .CAIRO_CAN_TEST_SVG_SURFACE = 1, + }); + feature_config.addValues(.{ + .CAIRO_HAS_SVG_SURFACE = 1, + .CAIRO_HAS_PNG_FUNCTIONS = 1, + }); + + try cairo_sources.appendSlice(b.allocator, sources.png); + + const freetype = b.dependency("freetype", .{ + .target = target, + .optimize = optimize, + }).artifact("freetype"); + lib.root_module.linkLibrary(freetype); + lib.installLibraryHeaders(freetype); + + feature_config.addValues(.{ .CAIRO_HAS_FT_FONT = 1 }); + config.addValues(.{ + .HAVE_FT_LOAD_NO_SVG = 1, + .HAVE_FT_SVG_DOCUMENT = 1, + .HAVE_FT_COLR_V1 = 1, + }); + + try cairo_sources.appendSlice(b.allocator, sources.freetype); + + if (target.result.os.tag == .windows) { + try c_flags.appendSlice(b.allocator, &.{ + "-DWIN32_LEAN_AND_MEAN", + "-DNOMINMAX", + "-DCAIRO_WIN32_STATIC_BUILD", + }); + + lib.root_module.linkSystemLibrary("gdi32", .{}); + lib.root_module.linkSystemLibrary("msimg32", .{}); + + feature_config.addValues(.{ + .CAIRO_HAS_WIN32_SURFACE = 1, + .CAIRO_HAS_WIN32_FONT = 1, + }); + } + + if (b.systemIntegrationOption("pixman", .{})) { + lib.root_module.linkSystemLibrary("pixman-1", .{}); + } else if (b.lazyDependency("pixman", .{ + .target = target, + .optimize = optimize, + })) |dep| { + const pixman = dep.artifact("pixman"); + pixman.root_module.sanitize_c = .off; + lib.root_module.linkLibrary(pixman); + lib.installLibraryHeaders(pixman); + } + + feature_config.addValues(.{ .CAIRO_HAS_IMAGE_SURFACE = 1 }); + config.addValues(.{ .HAS_PIXMAN_GLYPHS = 1 }); + + feature_config.addValues(.{ + .CAIRO_HAS_USER_FONT = 1, + .CAIRO_HAS_MIME_SURFACE = 1, + .CAIRO_HAS_RECORDING_SURFACE = 1, + .CAIRO_HAS_OBSERVER_SURFACE = 1, + }); + + lib.root_module.linkSystemLibrary("pthread", .{}); + + config.addValues(.{ + .CAIRO_HAS_PTHREAD = 1, + .CAIRO_HAS_REAL_PTHREAD = 1, + }); + + try c_flags.appendSlice(b.allocator, &.{ + "-pthread", + "-D_REENTRANT", + }); + + if (!target.result.cpu.arch.isX86()) + config.addValues(.{ .ATOMIC_OP_NEEDS_MEMORY_BARRIER = 1 }); + + lib.root_module.addConfigHeader(config); + lib.root_module.addConfigHeader(feature_config); + + lib.root_module.addCSourceFiles(.{ + .root = cairo.path("src"), + .files = try cairo_sources.toOwnedSlice(b.allocator), + .flags = c_flags.items, + }); + + lib.installHeadersDirectory(cairo.path("src"), "", .{}); + lib.installConfigHeader(config); + lib.installConfigHeader(feature_config); + + b.installArtifact(lib); +} diff --git a/pkg/cairo/build.zig.zon b/pkg/cairo/build.zig.zon new file mode 100644 index 000000000..ff5a94293 --- /dev/null +++ b/pkg/cairo/build.zig.zon @@ -0,0 +1,22 @@ +.{ + .name = .cairo_zig, + .version = "1.18.4", + .fingerprint = 0x71e19aedf148bc19, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + .cairo = .{ + .url = "https://www.cairographics.org/releases/cairo-1.18.4.tar.xz", + .hash = "N-V-__8AAFW9nwOvQAmLG664NOHF67FmS00qy_Lhg72aEv9Q", + }, + .freetype = .{ .path = "../freetype" }, + .zlib = .{ .path = "../zlib" }, + .pixman = .{ .path = "../pixman" }, + .libpng = .{ .path = "../libpng" }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "sources.zig", + "pkg", + }, +} diff --git a/pkg/cairo/sources.zig b/pkg/cairo/sources.zig new file mode 100644 index 000000000..ab4e8e90a --- /dev/null +++ b/pkg/cairo/sources.zig @@ -0,0 +1,138 @@ +pub const cairo = &.{ + "cairo.c", + "cairo-analysis-surface.c", + "cairo-arc.c", + "cairo-array.c", + "cairo-atomic.c", + "cairo-base64-stream.c", + "cairo-base85-stream.c", + "cairo-bentley-ottmann-rectangular.c", + "cairo-bentley-ottmann-rectilinear.c", + "cairo-bentley-ottmann.c", + "cairo-botor-scan-converter.c", + "cairo-boxes-intersect.c", + "cairo-boxes.c", + "cairo-cache.c", + "cairo-clip-boxes.c", + "cairo-clip-polygon.c", + "cairo-clip-region.c", + "cairo-clip-surface.c", + "cairo-clip-tor-scan-converter.c", + "cairo-clip.c", + "cairo-color.c", + "cairo-composite-rectangles.c", + "cairo-compositor.c", + "cairo-contour.c", + "cairo-damage.c", + "cairo-debug.c", + "cairo-default-context.c", + "cairo-device.c", + "cairo-error.c", + "cairo-fallback-compositor.c", + "cairo-fixed.c", + "cairo-font-face-twin-data.c", + "cairo-font-face-twin.c", + "cairo-font-face.c", + "cairo-font-options.c", + "cairo-freed-pool.c", + "cairo-freelist.c", + "cairo-gstate.c", + "cairo-hash.c", + "cairo-hull.c", + "cairo-image-compositor.c", + "cairo-image-info.c", + "cairo-image-source.c", + "cairo-image-surface.c", + "cairo-line.c", + "cairo-lzw.c", + "cairo-mask-compositor.c", + "cairo-matrix.c", + "cairo-mempool.c", + "cairo-mesh-pattern-rasterizer.c", + "cairo-misc.c", + "cairo-mono-scan-converter.c", + "cairo-mutex.c", + "cairo-no-compositor.c", + "cairo-observer.c", + "cairo-output-stream.c", + "cairo-paginated-surface.c", + "cairo-path-bounds.c", + "cairo-path-fill.c", + "cairo-path-fixed.c", + "cairo-path-in-fill.c", + "cairo-path-stroke-boxes.c", + "cairo-path-stroke-polygon.c", + "cairo-path-stroke-traps.c", + "cairo-path-stroke-tristrip.c", + "cairo-path-stroke.c", + "cairo-path.c", + "cairo-pattern.c", + "cairo-pen.c", + "cairo-polygon-intersect.c", + "cairo-polygon-reduce.c", + "cairo-polygon.c", + "cairo-raster-source-pattern.c", + "cairo-recording-surface.c", + "cairo-rectangle.c", + "cairo-rectangular-scan-converter.c", + "cairo-region.c", + "cairo-rtree.c", + "cairo-scaled-font.c", + "cairo-shape-mask-compositor.c", + "cairo-slope.c", + "cairo-spans-compositor.c", + "cairo-spans.c", + "cairo-spline.c", + "cairo-stroke-dash.c", + "cairo-stroke-style.c", + "cairo-surface-clipper.c", + "cairo-surface-fallback.c", + "cairo-surface-observer.c", + "cairo-surface-offset.c", + "cairo-surface-snapshot.c", + "cairo-surface-subsurface.c", + "cairo-surface-wrapper.c", + "cairo-surface.c", + "cairo-time.c", + "cairo-tor-scan-converter.c", + "cairo-tor22-scan-converter.c", + "cairo-toy-font-face.c", + "cairo-traps-compositor.c", + "cairo-traps.c", + "cairo-tristrip.c", + "cairo-unicode.c", + "cairo-user-font.c", + "cairo-version.c", + "cairo-wideint.c", + "cairo.c", + "cairo-cff-subset.c", + "cairo-scaled-font-subsets.c", + "cairo-truetype-subset.c", + "cairo-type1-fallback.c", + "cairo-type1-glyph-names.c", + "cairo-type1-subset.c", + "cairo-type3-glyph-surface.c", + "cairo-pdf-operators.c", + "cairo-pdf-shading.c", + "cairo-tag-attributes.c", + "cairo-tag-stack.c", + "cairo-deflate-stream.c", +}; + +pub const script_surface = &.{ + "cairo-script-surface.c", + "cairo-ps-surface.c", + "cairo-pdf-surface.c", + "cairo-pdf-interchange.c", +}; + +pub const png = &.{ + "cairo-png.c", + "cairo-svg-surface.c", +}; + +pub const freetype = &.{ + "cairo-ft-font.c", + "cairo-colr-glyph-render.c", + "cairo-svg-glyph-render.c", +}; diff --git a/pkg/freetype/build.zig b/pkg/freetype/build.zig new file mode 100644 index 000000000..2e97bf930 --- /dev/null +++ b/pkg/freetype/build.zig @@ -0,0 +1,124 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const upstream = b.dependency("freetype", .{}); + const lib = b.addLibrary(.{ + .name = "freetype", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = true, + }), + }); + + lib.root_module.addIncludePath(upstream.path("include")); + lib.installHeadersDirectory(upstream.path("include"), "", .{}); + + // Dependencies + const zlib_dep = b.dependency("zlib", .{ .target = target, .optimize = optimize }); + lib.root_module.linkLibrary(zlib_dep.artifact("z")); + + const libpng_dep = b.dependency("libpng", .{ .target = target, .optimize = optimize }); + lib.root_module.linkLibrary(libpng_dep.artifact("png")); + + var flags: std.ArrayList([]const u8) = .empty; + defer flags.deinit(b.allocator); + + try flags.appendSlice(b.allocator, &.{ + "-DFT2_BUILD_LIBRARY", + + "-DFT_CONFIG_OPTION_SYSTEM_ZLIB", + "-DFT_CONFIG_OPTION_USE_PNG", + + "-DHAVE_UNISTD_H", + "-DHAVE_FCNTL_H", + + "-fno-sanitize=undefined", + }); + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path(""), + .files = srcs, + .flags = flags.items, + }); + + switch (target.result.os.tag) { + .linux => lib.root_module.addCSourceFile(.{ + .file = upstream.path("builds/unix/ftsystem.c"), + .flags = flags.items, + }), + .windows => lib.root_module.addCSourceFile(.{ + .file = upstream.path("builds/windows/ftsystem.c"), + .flags = flags.items, + }), + else => lib.root_module.addCSourceFile(.{ + .file = upstream.path("src/base/ftsystem.c"), + .flags = flags.items, + }), + } + switch (target.result.os.tag) { + .windows => { + lib.root_module.addCSourceFile(.{ + .file = upstream.path("builds/windows/ftdebug.c"), + .flags = flags.items, + }); + lib.root_module.addWin32ResourceFile(.{ + .file = upstream.path("src/base/ftver.rc"), + }); + }, + else => lib.root_module.addCSourceFile(.{ + .file = upstream.path("src/base/ftdebug.c"), + .flags = flags.items, + }), + } + + b.installArtifact(lib); +} + +const srcs: []const []const u8 = &.{ + "src/autofit/autofit.c", + "src/base/ftbase.c", + "src/base/ftbbox.c", + "src/base/ftbdf.c", + "src/base/ftbitmap.c", + "src/base/ftcid.c", + "src/base/ftfstype.c", + "src/base/ftgasp.c", + "src/base/ftglyph.c", + "src/base/ftgxval.c", + "src/base/ftinit.c", + "src/base/ftmm.c", + "src/base/ftotval.c", + "src/base/ftpatent.c", + "src/base/ftpfr.c", + "src/base/ftstroke.c", + "src/base/ftsynth.c", + "src/base/fttype1.c", + "src/base/ftwinfnt.c", + "src/bdf/bdf.c", + "src/bzip2/ftbzip2.c", + "src/cache/ftcache.c", + "src/cff/cff.c", + "src/cid/type1cid.c", + "src/gzip/ftgzip.c", + "src/lzw/ftlzw.c", + "src/pcf/pcf.c", + "src/pfr/pfr.c", + "src/psaux/psaux.c", + "src/pshinter/pshinter.c", + "src/psnames/psnames.c", + "src/raster/raster.c", + "src/sdf/sdf.c", + "src/sfnt/sfnt.c", + "src/smooth/smooth.c", + "src/svg/svg.c", + "src/truetype/truetype.c", + "src/type1/type1.c", + "src/type42/type42.c", + "src/winfonts/winfnt.c", +}; diff --git a/pkg/freetype/build.zig.zon b/pkg/freetype/build.zig.zon new file mode 100644 index 000000000..2fc664792 --- /dev/null +++ b/pkg/freetype/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .freetype, + .fingerprint = 0xac2059b6c1cddc9f, + .version = "0.0.0", + .dependencies = .{ + .freetype = .{ + .url = "https://github.com/freetype/freetype/archive/refs/tags/VER-2-14-1.tar.gz", + .hash = "N-V-__8AAJx1qgDwI7KTM6yDZ4ArrZU6qmKKuvDHRVrxUkTw", + }, + .zlib = .{ .path = "../zlib" }, + .libpng = .{ .path = "../libpng" }, + }, +} diff --git a/pkg/giflib/build.zig b/pkg/giflib/build.zig new file mode 100644 index 000000000..6cf32e15a --- /dev/null +++ b/pkg/giflib/build.zig @@ -0,0 +1,41 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const giflib = b.addLibrary(.{ + .name = "giflib", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = true, + }) + }); + + const upstream = b.dependency("giflib", .{}); + + giflib.root_module.addCSourceFiles(.{ + .root = upstream.path("."), + .files = &.{ + "dgif_lib.c", + "egif_lib.c", + "gifalloc.c", + "gif_err.c", + "gif_font.c", + "gif_hash.c", + "openbsd-reallocarray.c", + } , + .flags = &.{ + "-std=gnu99", + "-Wall", + "-Wno-format-truncation", + } + }); + + giflib.installHeadersDirectory(upstream.path("."), "", .{}); + + b.installArtifact(giflib); +} diff --git a/pkg/giflib/build.zig.zon b/pkg/giflib/build.zig.zon new file mode 100644 index 000000000..b624e5442 --- /dev/null +++ b/pkg/giflib/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .giflib, + .version = "5.2.2", + .fingerprint = 0x7812c17c3a32a94d, + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .giflib = .{ + .url = "https://downloads.sourceforge.net/project/giflib/giflib-5.x/giflib-5.2.2.tar.gz", + .hash = "N-V-__8AAI4GIwD_4QAQ2PcKUSZ8F2XH_vDa4da8cUxuuOU9", + }, + }, + .paths = .{""}, +} diff --git a/pkg/harfbuzz/build.zig b/pkg/harfbuzz/build.zig new file mode 100644 index 000000000..0e979c795 --- /dev/null +++ b/pkg/harfbuzz/build.zig @@ -0,0 +1,26 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const upstream = b.dependency("harfbuzz", .{}); + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const harfbuzz = b.addLibrary(.{ + .name = "harfbuzz", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + .pic = true, + }) + }); + + harfbuzz.root_module.addCSourceFile(.{ + .file = upstream.path("src/harfbuzz.cc"), + }); + harfbuzz.installHeadersDirectory(upstream.path("src"), "", .{}); + + b.installArtifact(harfbuzz); +} diff --git a/pkg/harfbuzz/build.zig.zon b/pkg/harfbuzz/build.zig.zon new file mode 100644 index 000000000..08a1d32fa --- /dev/null +++ b/pkg/harfbuzz/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .harfbuzz, + .fingerprint = 0xbd60917c14346ca6, + .version = "0.0.0", + .dependencies = .{ + .harfbuzz = .{ + .url = "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/12.3.0.tar.gz", + .hash = "N-V-__8AAK5WlgUb7zJouNiGizHTUdl6zQTgr_apzjhQfiwE", + } + }, +} diff --git a/pkg/libpng/LICENSE b/pkg/libpng/LICENSE new file mode 100644 index 000000000..5baa1c05b --- /dev/null +++ b/pkg/libpng/LICENSE @@ -0,0 +1,20 @@ +Copyright © Mitchell Hashimoto +Copyright © Andrew Kelley + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pkg/libpng/build.zig b/pkg/libpng/build.zig new file mode 100644 index 000000000..29f6ffb16 --- /dev/null +++ b/pkg/libpng/build.zig @@ -0,0 +1,69 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const upstream = b.dependency("libpng", .{}); + + const mod = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = true, + }); + + const lib = b.addLibrary(.{ + .name = "png", + .linkage = .static, + .root_module = mod, + }); + + const zlib_dep = b.dependency("zlib", .{ .target = target, .optimize = optimize }); + mod.linkLibrary(zlib_dep.artifact("z")); + mod.addIncludePath(upstream.path("")); + mod.addIncludePath(b.path("")); + + var flags: std.ArrayList([]const u8) = .empty; + defer flags.deinit(b.allocator); + + try flags.appendSlice(b.allocator, &.{ + "-DPNG_ARM_NEON_OPT=0", + "-DPNG_POWERPC_VSX_OPT=0", + "-DPNG_INTEL_SSE_OPT=0", + "-DPNG_MIPS_MSA_OPT=0", + }); + + mod.addCSourceFiles(.{ + .root = upstream.path(""), + .files = srcs, + .flags = flags.items, + }); + + lib.installHeader(b.path("pnglibconf.h"), "pnglibconf.h"); + lib.installHeadersDirectory( + upstream.path("."), + "", + .{ .include_extensions = &.{".h"} }, + ); + + b.installArtifact(lib); +} + +const srcs: []const []const u8 = &.{ + "png.c", + "pngerror.c", + "pngget.c", + "pngmem.c", + "pngpread.c", + "pngread.c", + "pngrio.c", + "pngrtran.c", + "pngrutil.c", + "pngset.c", + "pngtrans.c", + "pngwio.c", + "pngwrite.c", + "pngwtran.c", + "pngwutil.c", +}; diff --git a/pkg/libpng/build.zig.zon b/pkg/libpng/build.zig.zon new file mode 100644 index 000000000..4e7bd7ab5 --- /dev/null +++ b/pkg/libpng/build.zig.zon @@ -0,0 +1,19 @@ +.{ + .name = .libpng, + .version = "1.6.57", + .minimum_zig_version = "0.15.1", + .fingerprint = 0xb7a09eb41a8526a2, + .paths = .{ + "LICENSE", + "build.zig", + "build.zig.zon", + "pnglibconf.h", + }, + .dependencies = .{ + .libpng = .{ + .url = "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.57.tar.gz", + .hash = "N-V-__8AACSdYAB_UbYSe21ZcD-qFBX7twJ4mRDPTrekTXQF", + }, + .zlib = .{ .path = "../zlib" }, + }, +} diff --git a/pkg/libpng/pnglibconf.h b/pkg/libpng/pnglibconf.h new file mode 100644 index 000000000..b39c38c71 --- /dev/null +++ b/pkg/libpng/pnglibconf.h @@ -0,0 +1,219 @@ +/* pnglibconf.h - library build configuration */ + +/* libpng version 1.6.38.git */ + +/* Copyright (c) 2018-2020 Cosmin Truta */ +/* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */ + +/* This code is released under the libpng license. */ +/* For conditions of distribution and use, see the disclaimer */ +/* and license in png.h */ + +/* pnglibconf.h */ +/* Machine generated file: DO NOT EDIT */ +/* Derived from: scripts/pnglibconf.dfa */ +#ifndef PNGLCONF_H +#define PNGLCONF_H +/* options */ +#define PNG_16BIT_SUPPORTED +#define PNG_ALIGNED_MEMORY_SUPPORTED +/*#undef PNG_ARM_NEON_API_SUPPORTED*/ +/*#undef PNG_ARM_NEON_CHECK_SUPPORTED*/ +#define PNG_BENIGN_ERRORS_SUPPORTED +#define PNG_BENIGN_READ_ERRORS_SUPPORTED +/*#undef PNG_BENIGN_WRITE_ERRORS_SUPPORTED*/ +#define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +#define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_COLORSPACE_SUPPORTED +#define PNG_CONSOLE_IO_SUPPORTED +#define PNG_CONVERT_tIME_SUPPORTED +#define PNG_EASY_ACCESS_SUPPORTED +/*#undef PNG_ERROR_NUMBERS_SUPPORTED*/ +#define PNG_ERROR_TEXT_SUPPORTED +#define PNG_FIXED_POINT_SUPPORTED +#define PNG_FLOATING_ARITHMETIC_SUPPORTED +#define PNG_FLOATING_POINT_SUPPORTED +#define PNG_FORMAT_AFIRST_SUPPORTED +#define PNG_FORMAT_BGR_SUPPORTED +#define PNG_GAMMA_SUPPORTED +#define PNG_GET_PALETTE_MAX_SUPPORTED +#define PNG_HANDLE_AS_UNKNOWN_SUPPORTED +#define PNG_INCH_CONVERSIONS_SUPPORTED +#define PNG_INFO_IMAGE_SUPPORTED +#define PNG_IO_STATE_SUPPORTED +#define PNG_MNG_FEATURES_SUPPORTED +#define PNG_POINTER_INDEXING_SUPPORTED +/*#undef PNG_POWERPC_VSX_API_SUPPORTED*/ +/*#undef PNG_POWERPC_VSX_CHECK_SUPPORTED*/ +#define PNG_PROGRESSIVE_READ_SUPPORTED +#define PNG_READ_16BIT_SUPPORTED +#define PNG_READ_ALPHA_MODE_SUPPORTED +#define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_READ_BACKGROUND_SUPPORTED +#define PNG_READ_BGR_SUPPORTED +#define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_READ_COMPOSITE_NODIV_SUPPORTED +#define PNG_READ_COMPRESSED_TEXT_SUPPORTED +#define PNG_READ_EXPAND_16_SUPPORTED +#define PNG_READ_EXPAND_SUPPORTED +#define PNG_READ_FILLER_SUPPORTED +#define PNG_READ_GAMMA_SUPPORTED +#define PNG_READ_GET_PALETTE_MAX_SUPPORTED +#define PNG_READ_GRAY_TO_RGB_SUPPORTED +#define PNG_READ_INTERLACING_SUPPORTED +#define PNG_READ_INT_FUNCTIONS_SUPPORTED +#define PNG_READ_INVERT_ALPHA_SUPPORTED +#define PNG_READ_INVERT_SUPPORTED +#define PNG_READ_OPT_PLTE_SUPPORTED +#define PNG_READ_PACKSWAP_SUPPORTED +#define PNG_READ_PACK_SUPPORTED +#define PNG_READ_QUANTIZE_SUPPORTED +#define PNG_READ_RGB_TO_GRAY_SUPPORTED +#define PNG_READ_SCALE_16_TO_8_SUPPORTED +#define PNG_READ_SHIFT_SUPPORTED +#define PNG_READ_STRIP_16_TO_8_SUPPORTED +#define PNG_READ_STRIP_ALPHA_SUPPORTED +#define PNG_READ_SUPPORTED +#define PNG_READ_SWAP_ALPHA_SUPPORTED +#define PNG_READ_SWAP_SUPPORTED +#define PNG_READ_TEXT_SUPPORTED +#define PNG_READ_TRANSFORMS_SUPPORTED +#define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_READ_USER_CHUNKS_SUPPORTED +#define PNG_READ_USER_TRANSFORM_SUPPORTED +#define PNG_READ_bKGD_SUPPORTED +#define PNG_READ_cHRM_SUPPORTED +#define PNG_READ_eXIf_SUPPORTED +#define PNG_READ_gAMA_SUPPORTED +#define PNG_READ_hIST_SUPPORTED +#define PNG_READ_iCCP_SUPPORTED +#define PNG_READ_iTXt_SUPPORTED +#define PNG_READ_oFFs_SUPPORTED +#define PNG_READ_pCAL_SUPPORTED +#define PNG_READ_pHYs_SUPPORTED +#define PNG_READ_sBIT_SUPPORTED +#define PNG_READ_sCAL_SUPPORTED +#define PNG_READ_sPLT_SUPPORTED +#define PNG_READ_sRGB_SUPPORTED +#define PNG_READ_tEXt_SUPPORTED +#define PNG_READ_tIME_SUPPORTED +#define PNG_READ_tRNS_SUPPORTED +#define PNG_READ_zTXt_SUPPORTED +#define PNG_SAVE_INT_32_SUPPORTED +#define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SEQUENTIAL_READ_SUPPORTED +#define PNG_SETJMP_SUPPORTED +#define PNG_SET_OPTION_SUPPORTED +#define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SET_USER_LIMITS_SUPPORTED +#define PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED +#define PNG_SIMPLIFIED_READ_BGR_SUPPORTED +#define PNG_SIMPLIFIED_READ_SUPPORTED +#define PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED +#define PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED +#define PNG_SIMPLIFIED_WRITE_STDIO_SUPPORTED +#define PNG_SIMPLIFIED_WRITE_SUPPORTED +#define PNG_STDIO_SUPPORTED +#define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_TEXT_SUPPORTED +#define PNG_TIME_RFC1123_SUPPORTED +#define PNG_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_USER_CHUNKS_SUPPORTED +#define PNG_USER_LIMITS_SUPPORTED +#define PNG_USER_MEM_SUPPORTED +#define PNG_USER_TRANSFORM_INFO_SUPPORTED +#define PNG_USER_TRANSFORM_PTR_SUPPORTED +#define PNG_WARNINGS_SUPPORTED +#define PNG_WRITE_16BIT_SUPPORTED +#define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_WRITE_BGR_SUPPORTED +#define PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_WRITE_COMPRESSED_TEXT_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +#define PNG_WRITE_FILLER_SUPPORTED +#define PNG_WRITE_FILTER_SUPPORTED +#define PNG_WRITE_FLUSH_SUPPORTED +#define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED +#define PNG_WRITE_INTERLACING_SUPPORTED +#define PNG_WRITE_INT_FUNCTIONS_SUPPORTED +#define PNG_WRITE_INVERT_ALPHA_SUPPORTED +#define PNG_WRITE_INVERT_SUPPORTED +#define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED +#define PNG_WRITE_PACKSWAP_SUPPORTED +#define PNG_WRITE_PACK_SUPPORTED +#define PNG_WRITE_SHIFT_SUPPORTED +#define PNG_WRITE_SUPPORTED +#define PNG_WRITE_SWAP_ALPHA_SUPPORTED +#define PNG_WRITE_SWAP_SUPPORTED +#define PNG_WRITE_TEXT_SUPPORTED +#define PNG_WRITE_TRANSFORMS_SUPPORTED +#define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_WRITE_USER_TRANSFORM_SUPPORTED +#define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED +#define PNG_WRITE_bKGD_SUPPORTED +#define PNG_WRITE_cHRM_SUPPORTED +#define PNG_WRITE_eXIf_SUPPORTED +#define PNG_WRITE_gAMA_SUPPORTED +#define PNG_WRITE_hIST_SUPPORTED +#define PNG_WRITE_iCCP_SUPPORTED +#define PNG_WRITE_iTXt_SUPPORTED +#define PNG_WRITE_oFFs_SUPPORTED +#define PNG_WRITE_pCAL_SUPPORTED +#define PNG_WRITE_pHYs_SUPPORTED +#define PNG_WRITE_sBIT_SUPPORTED +#define PNG_WRITE_sCAL_SUPPORTED +#define PNG_WRITE_sPLT_SUPPORTED +#define PNG_WRITE_sRGB_SUPPORTED +#define PNG_WRITE_tEXt_SUPPORTED +#define PNG_WRITE_tIME_SUPPORTED +#define PNG_WRITE_tRNS_SUPPORTED +#define PNG_WRITE_zTXt_SUPPORTED +#define PNG_bKGD_SUPPORTED +#define PNG_cHRM_SUPPORTED +#define PNG_eXIf_SUPPORTED +#define PNG_gAMA_SUPPORTED +#define PNG_hIST_SUPPORTED +#define PNG_iCCP_SUPPORTED +#define PNG_iTXt_SUPPORTED +#define PNG_oFFs_SUPPORTED +#define PNG_pCAL_SUPPORTED +#define PNG_pHYs_SUPPORTED +#define PNG_sBIT_SUPPORTED +#define PNG_sCAL_SUPPORTED +#define PNG_sPLT_SUPPORTED +#define PNG_sRGB_SUPPORTED +#define PNG_tEXt_SUPPORTED +#define PNG_tIME_SUPPORTED +#define PNG_tRNS_SUPPORTED +#define PNG_zTXt_SUPPORTED +/* end of options */ +/* settings */ +#define PNG_API_RULE 0 +#define PNG_DEFAULT_READ_MACROS 1 +#define PNG_GAMMA_THRESHOLD_FIXED 5000 +#define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE +#define PNG_INFLATE_BUF_SIZE 1024 +#define PNG_LINKAGE_API extern +#define PNG_LINKAGE_CALLBACK extern +#define PNG_LINKAGE_DATA extern +#define PNG_LINKAGE_FUNCTION extern +#define PNG_MAX_GAMMA_8 11 +#define PNG_QUANTIZE_BLUE_BITS 5 +#define PNG_QUANTIZE_GREEN_BITS 5 +#define PNG_QUANTIZE_RED_BITS 5 +#define PNG_TEXT_Z_DEFAULT_COMPRESSION (-1) +#define PNG_TEXT_Z_DEFAULT_STRATEGY 0 +#define PNG_USER_CHUNK_CACHE_MAX 1000 +#define PNG_USER_CHUNK_MALLOC_MAX 8000000 +#define PNG_USER_HEIGHT_MAX 1000000 +#define PNG_USER_WIDTH_MAX 1000000 +#define PNG_ZBUF_SIZE 8192 +#define PNG_ZLIB_VERNUM 0 /* unknown */ +#define PNG_Z_DEFAULT_COMPRESSION (-1) +#define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0 +#define PNG_Z_DEFAULT_STRATEGY 1 +#define PNG_sCAL_PRECISION 5 +#define PNG_sRGB_PROFILE_CHECKS 2 +/* end of settings */ +#endif /* PNGLCONF_H */ diff --git a/pkg/lunasvg/build.zig b/pkg/lunasvg/build.zig new file mode 100644 index 000000000..60c9ddc9c --- /dev/null +++ b/pkg/lunasvg/build.zig @@ -0,0 +1,111 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const plutovg = b.addLibrary(.{ + .name = "plutovg", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .pic = true, + .link_libc = true, + }) + }); + + const upstream = b.dependency("lunasvg", .{}); + const cairo = b.dependency("cairo", .{ + .target = target, + .optimize = optimize, + }).artifact("cairo"); + + plutovg.root_module.addCSourceFiles(.{ + .root = upstream.path(""), + .files = &.{ + "plutovg/source/plutovg-blend.c", + "plutovg/source/plutovg-canvas.c", + "plutovg/source/plutovg-font.c", + "plutovg/source/plutovg-matrix.c", + "plutovg/source/plutovg-path.c", + "plutovg/source/plutovg-paint.c", + "plutovg/source/plutovg-rasterize.c", + "plutovg/source/plutovg-surface.c", + "plutovg/source/plutovg-ft-math.c", + "plutovg/source/plutovg-ft-raster.c", + "plutovg/source/plutovg-ft-stroker.c" + }, + .flags = &.{ + "-D_FILE_OFFSET_BITS=64", + "-std=gnu11", + "-pthread", + "-DWITH_GZFILEOP", + "-DPLUTOVG_BUILD", + "-DPLUTOVG_BUILD_STATIC", + "-DHAVE_THREADS_H", + "-fvisibility=hidden", + } + }); + + plutovg.root_module.addIncludePath(upstream.path("plutovg/include")); + plutovg.installHeadersDirectory(upstream.path("plutovg/include"), "", .{}); + plutovg.root_module.linkLibrary(cairo); + + // strip out anything we didn't use, which for plutovg is a lot (on this fork) + plutovg.link_data_sections = true; + plutovg.link_function_sections = true; + + const lunasvg = b.addLibrary(.{ + .name = "lunasvg", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .pic = true, + .link_libc = true, + .link_libcpp = true, + }) + }); + + lunasvg.root_module.addCSourceFiles(.{ + .root = upstream.path(""), + .files = &.{ + "source/lunasvg.cpp", + "source/graphics.cpp", + "source/svgelement.cpp", + "source/svggeometryelement.cpp", + "source/svgpaintelement.cpp", + "source/svgparser.cpp", + "source/svgproperty.cpp", + "source/svglayoutstate.cpp", + "source/svgrenderstate.cpp", + "source/svgtextelement.cpp" + }, + .flags = &.{ + "-D_GLIBCXX_ASSERTIONS=1", + "-D_FILE_OFFSET_BITS=64", + "-std=c++17", + "-DWITH_GZFILEOP", + "-pthread", + "-DLUNASVG_BUILD", + "-DLUNASVG_BUILD_STATIC", + "-DPLUTOVG_BUILD_STATIC", + "-fvisibility=hidden", + // dlclose gets called on the addon + // https://maskray.me/blog/2024-03-17-c++-exit-time-destructors + "-fno-c++-static-destructors", + } + }); + + // strip out anything we didn't use + lunasvg.link_data_sections = true; + lunasvg.link_function_sections = true; + + lunasvg.root_module.addIncludePath(upstream.path("include")); + lunasvg.installHeadersDirectory(upstream.path("include"), "", .{}); + lunasvg.root_module.linkLibrary(plutovg); + lunasvg.root_module.linkLibrary(cairo); + + b.installArtifact(lunasvg); +} diff --git a/pkg/lunasvg/build.zig.zon b/pkg/lunasvg/build.zig.zon new file mode 100644 index 000000000..30ddec25c --- /dev/null +++ b/pkg/lunasvg/build.zig.zon @@ -0,0 +1,12 @@ +.{ + .name = .lunasvg, + .fingerprint = 0xd309c055ae5fca1b, + .version = "0.0.0", + .dependencies = .{ + .cairo = .{ .path = "../cairo" }, + .lunasvg = .{ + .url = "https://github.com/chearon/lunasvg/archive/3fd8ba9e0cd5cf0af36b1bda2d0a3bbaf8bd8da7.tar.gz", + .hash = "N-V-__8AAFD4FABzdNrtG-6ulAVAPIPJZde38BQWKRcKgkgV", + } + }, +} diff --git a/pkg/pixman/build.zig b/pkg/pixman/build.zig new file mode 100644 index 000000000..3e4d42f71 --- /dev/null +++ b/pkg/pixman/build.zig @@ -0,0 +1,68 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const upstream = b.dependency("pixman", .{}); + + const lib = b.addLibrary(.{ + .name = "pixman", + .root_module = b.createModule(.{ + .target = b.standardTargetOptions(.{}), + .optimize = b.standardOptimizeOption(.{}), + .link_libc = true, + .pic = true, + }), + }); + + lib.root_module.addCMacro("HAVE_CONFIG_H", "1"); + + const config_h = b.addConfigHeader(.{ + .include_path = "config.h", + }, .{ + // Required to make pixman-private.h + .PACKAGE = "FOO", + .HAVE_PTHREADS = "1", + }); + lib.root_module.addConfigHeader(config_h); + + lib.root_module.addIncludePath(upstream.path("pixman")); + lib.installHeadersDirectory(upstream.path("pixman"), "", .{}); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("pixman"), + .files = sources, + }); + + b.installArtifact(lib); +} + +const sources = &.{ + "pixman.c", + "pixman-access.c", + "pixman-access-accessors.c", + "pixman-bits-image.c", + "pixman-combine32.c", + "pixman-combine-float.c", + "pixman-conical-gradient.c", + "pixman-filter.c", + "pixman-x86.c", + "pixman-mips.c", + "pixman-arm.c", + "pixman-ppc.c", + "pixman-edge.c", + "pixman-edge-accessors.c", + "pixman-fast-path.c", + "pixman-glyph.c", + "pixman-general.c", + "pixman-gradient-walker.c", + "pixman-image.c", + "pixman-implementation.c", + "pixman-linear-gradient.c", + "pixman-matrix.c", + "pixman-noop.c", + "pixman-radial-gradient.c", + "pixman-region16.c", + "pixman-region32.c", + "pixman-solid-fill.c", + "pixman-timer.c", + "pixman-trap.c", + "pixman-utils.c", +}; diff --git a/pkg/pixman/build.zig.zon b/pkg/pixman/build.zig.zon new file mode 100644 index 000000000..aaaefe0ab --- /dev/null +++ b/pkg/pixman/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .pixman, + .fingerprint = 0x16aa7fe7133d4f93, + .version = "0.0.0", + .dependencies = .{ + .pixman = .{ + .url = "https://cairographics.org/releases/pixman-0.42.2.tar.gz", + .hash = "N-V-__8AACdzQgB3Xt60BggeOhQybqmqPiqfBgmabFAL8LPJ", + }, + }, +} diff --git a/pkg/sheenbidi/build.zig b/pkg/sheenbidi/build.zig new file mode 100644 index 000000000..e4f4bafd1 --- /dev/null +++ b/pkg/sheenbidi/build.zig @@ -0,0 +1,28 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const upstream = b.dependency("sheenbidi", .{}); + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const sheenbidi = b.addLibrary(.{ + .name = "sheenbidi", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .pic = true, + .link_libc = true, + }) + }); + + sheenbidi.root_module.addCSourceFile(.{ + .file = upstream.path("Source/SheenBidi.c"), + .flags = &.{"-DSB_CONFIG_UNITY"}, + }); + + sheenbidi.root_module.addIncludePath(upstream.path("Headers")); + sheenbidi.installHeadersDirectory(upstream.path("Headers"), "", .{}); + + b.installArtifact(sheenbidi); +} diff --git a/pkg/sheenbidi/build.zig.zon b/pkg/sheenbidi/build.zig.zon new file mode 100644 index 000000000..461572b37 --- /dev/null +++ b/pkg/sheenbidi/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .sheenbidi, + .fingerprint = 0x684adc4fa57f4871, + .version = "0.0.0", + .dependencies = .{ + .sheenbidi = .{ + .url = "https://github.com/Tehreer/SheenBidi/archive/refs/tags/v2.9.0.tar.gz", + .hash = "N-V-__8AAHe8LwF6n4S3jDUlzJLIka4vXuAcDZnCnNpaR1-l" + } + }, +} diff --git a/pkg/zlib/LICENSE b/pkg/zlib/LICENSE new file mode 100644 index 000000000..06ce9e01b --- /dev/null +++ b/pkg/zlib/LICENSE @@ -0,0 +1,21 @@ +The MIT License (Expat) + +Copyright (c) contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pkg/zlib/build.zig b/pkg/zlib/build.zig new file mode 100644 index 000000000..03894cae4 --- /dev/null +++ b/pkg/zlib/build.zig @@ -0,0 +1,49 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const upstream = b.dependency("zlib", .{}); + const lib = b.addLibrary(.{ + .name = "z", + .linkage = .static, + .root_module = b.createModule(.{ + .target = b.standardTargetOptions(.{}), + .optimize = b.standardOptimizeOption(.{}), + .link_libc = true, + .pic = true, + }), + }); + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path(""), + .files = &.{ + "adler32.c", + "crc32.c", + "deflate.c", + "infback.c", + "inffast.c", + "inflate.c", + "inftrees.c", + "trees.c", + "zutil.c", + "compress.c", + "uncompr.c", + "gzclose.c", + "gzlib.c", + "gzread.c", + "gzwrite.c", + }, + .flags = &.{ + "-DHAVE_SYS_TYPES_H", + "-DHAVE_STDINT_H", + "-DHAVE_STDDEF_H", + "-DZ_HAVE_UNISTD_H", + } + }); + lib.installHeadersDirectory(upstream.path(""), "", .{ + .include_extensions = &.{ + "zconf.h", + "zlib.h", + }, + }); + b.installArtifact(lib); +} diff --git a/pkg/zlib/build.zig.zon b/pkg/zlib/build.zig.zon new file mode 100644 index 000000000..902b1b713 --- /dev/null +++ b/pkg/zlib/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .zlib, + .version = "1.3.2", + .fingerprint = 0x73887d3a953b9465, + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .zlib = .{ + .url = "https://github.com/madler/zlib/archive/refs/tags/v1.3.2.tar.gz", + .hash = "N-V-__8AAI4ANwDM8MWhHTUFLKfvLE_n2NQk6mh0sFDzhTqO", + }, + }, + .paths = .{ + "LICENSE", + "build.zig", + "build.zig.zon", + }, +} diff --git a/scripts/version.js b/scripts/version.js new file mode 100644 index 000000000..db6eb56fb --- /dev/null +++ b/scripts/version.js @@ -0,0 +1,68 @@ +const fs = require('fs'); +const path = require('path'); +const rVersion = /^(\d+)\.(\d+).(\d+)$/ +let __pjson; + +function getPackageJson() { + if (__pjson) return __pjson; + const buffer = fs.readFileSync(path.join(__dirname, "../package.json")); + __pjson = JSON.parse(buffer); + return __pjson; +} + +function getVersionOrExit() { + const pjson = getPackageJson(); + let version = process.argv[2]; + let match; + let parts; + + if (version === 'major') { + parts = rVersion.exec(pjson.version).slice(1); + parts[0]++; + } else if (version === 'minor') { + parts = rVersion.exec(pjson.version).slice(1); + parts[1]++; + } else if (version === 'patch') { + parts = rVersion.exec(pjson.version).slice(1); + parts[2]++; + } else if ((match = rVersion.exec(version))) { + parts = match.slice(1); + } + + if (parts) { + return parts.join('.'); + } else { + console.error('Usage: node scripts/version.js [major|minor|patch|]'); + process.exit(1); + } +} + +const version = getVersionOrExit(); +const pjson = getPackageJson(); +const wjsons = {}; + +pjson.version = version; + +for (const dep in pjson.optionalDependencies) { + const folder = dep.slice(7); // "canvas-" + try { + const buffer = fs.readFileSync(path.join(__dirname, `../npm/${folder}/package.json`)); + wjsons[dep] = JSON.parse(buffer); + } catch (e) { + console.error(`${dep} is in optionalDependencies, but npm/${folder}/package.json is not a JSON file`); + process.exit(1); + } + + pjson.optionalDependencies[dep] = version; + wjsons[dep].version = version; +} + +fs.writeFileSync(path.join(__dirname, "../package.json"), JSON.stringify(pjson, undefined, 2) + '\n'); +console.log(`canvas: ${version}`); + +for (const dep in wjsons) { + const folder = dep.slice(7); // "canvas-" + const wjson = wjsons[dep]; + fs.writeFileSync(path.join(__dirname, `../npm/${folder}/package.json`), JSON.stringify(wjson, undefined, 2) + '\n'); + console.log(`${dep}: ${version}`); +} diff --git a/src/Canvas.cc b/src/Canvas.cc index 0572a7557..c3877ae1f 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -11,36 +11,20 @@ #include #include #include -#include #include "PNG.h" -#include "register_font.h" #include #include #include #include #include "Util.h" #include -#include "node_buffer.h" #include "FontParser.h" - -#ifdef HAVE_JPEG #include "JPEGStream.h" -#endif - -#define GENERIC_FACE_ERROR \ - "The second argument to registerFont is required, and should be an object " \ - "with at least a family (string) and optionally weight (string/number) " \ - "and style (string)." #define CAIRO_MAX_SIZE 32767 using namespace std; -std::vector Canvas::font_face_list; - -// Increases each time a font is (de)registered -int Canvas::fontSerial = 1; - /* * Initialize Canvas. */ @@ -54,9 +38,7 @@ Canvas::Initialize(Napi::Env& env, Napi::Object& exports) { InstanceMethod<&Canvas::ToBuffer>("toBuffer", napi_default_method), InstanceMethod<&Canvas::StreamPNGSync>("streamPNGSync", napi_default_method), InstanceMethod<&Canvas::StreamPDFSync>("streamPDFSync", napi_default_method), -#ifdef HAVE_JPEG InstanceMethod<&Canvas::StreamJPEGSync>("streamJPEGSync", napi_default_method), -#endif InstanceAccessor<&Canvas::GetType>("type", napi_default_jsproperty), InstanceAccessor<&Canvas::GetStride>("stride", napi_default_jsproperty), InstanceAccessor<&Canvas::GetWidth, &Canvas::SetWidth>("width", napi_default_jsproperty), @@ -68,8 +50,6 @@ Canvas::Initialize(Napi::Env& env, Napi::Object& exports) { StaticValue("PNG_FILTER_AVG", Napi::Number::New(env, PNG_FILTER_AVG), napi_default_jsproperty), StaticValue("PNG_FILTER_PAETH", Napi::Number::New(env, PNG_FILTER_PAETH), napi_default_jsproperty), StaticValue("PNG_ALL_FILTERS", Napi::Number::New(env, PNG_ALL_FILTERS), napi_default_jsproperty), - StaticMethod<&Canvas::RegisterFont>("_registerFont", napi_default_method), - StaticMethod<&Canvas::DeregisterAllFonts>("_deregisterAllFonts", napi_default_method), StaticMethod<&Canvas::ParseFont>("parseFont", napi_default_method) }); @@ -224,13 +204,11 @@ Canvas::ToPngBufferAsync(Closure* base) { closure); } -#ifdef HAVE_JPEG void Canvas::ToJpegBufferAsync(Closure* base) { JpegClosure* closure = static_cast(base); write_to_jpeg_buffer(closure->canvas->ensureSurface(), closure); } -#endif static void parsePNGArgs(Napi::Value arg, PngClosure& pngargs) { @@ -275,7 +253,6 @@ parsePNGArgs(Napi::Value arg, PngClosure& pngargs) { } } -#ifdef HAVE_JPEG static void parseJPEGArgs(Napi::Value arg, JpegClosure& jpegargs) { // "If Type(quality) is not Number, or if quality is outside that range, the // user agent must use its default quality value, as if the quality argument @@ -307,9 +284,6 @@ static void parseJPEGArgs(Napi::Value arg, JpegClosure& jpegargs) { } } } -#endif - -#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) static inline void setPdfMetaStr(cairo_surface_t* surf, Napi::Object opts, cairo_pdf_metadata_t t, const char* propName) { @@ -343,8 +317,6 @@ static void setPdfMetadata(Canvas* canvas, Napi::Object opts) { setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_MOD_DATE, "modDate"); } -#endif // CAIRO 16+ - /* * Converts/encodes data to a Buffer. Async when a callback function is passed. @@ -380,11 +352,9 @@ Canvas::ToBuffer(const Napi::CallbackInfo& info) { // mime type may be present, but it's not checked PdfSvgClosure* closure = static_cast(_closure); if (isPDF()) { -#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) if (info[1].IsObject()) { // toBuffer("application/pdf", config) setPdfMetadata(this, info[1].As()); } -#endif // CAIRO 16+ } cairo_surface_t *surf = ensureSurface(); @@ -403,10 +373,6 @@ Canvas::ToBuffer(const Napi::CallbackInfo& info) { if (info[0].StrictEquals(Napi::String::New(env, "raw"))) { cairo_surface_t *surface = ensureSurface(); cairo_surface_flush(surface); - if (nBytes() > node::Buffer::kMaxLength) { - Napi::Error::New(env, "Data exceeds maximum buffer length.").ThrowAsJavaScriptException(); - return env.Undefined(); - } return Napi::Buffer::Copy(env, cairo_image_surface_get_data(surface), nBytes()); } @@ -467,7 +433,6 @@ Canvas::ToBuffer(const Napi::CallbackInfo& info) { return env.Undefined(); } -#ifdef HAVE_JPEG // Sync JPEG Napi::Value jpegStr = Napi::String::New(env, "image/jpeg"); if (info[0].StrictEquals(jpegStr)) { @@ -503,7 +468,6 @@ Canvas::ToBuffer(const Napi::CallbackInfo& info) { worker->Queue(); return env.Undefined(); } -#endif return env.Undefined(); } @@ -608,11 +572,9 @@ Canvas::StreamPDFSync(const Napi::CallbackInfo& info) { return; } -#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) if (info[1].IsObject()) { setPdfMetadata(this, info[1].As()); } -#endif cairo_surface_finish(ensureSurface()); @@ -638,7 +600,6 @@ Canvas::StreamPDFSync(const Napi::CallbackInfo& info) { * Stream JPEG data synchronously. */ -#ifdef HAVE_JPEG static uint32_t getSafeBufSize(Canvas* canvas) { // Don't allow the buffer size to exceed the size of the canvas (#674) // TODO not sure if this is really correct, but it fixed #674 @@ -659,7 +620,6 @@ Canvas::StreamJPEGSync(const Napi::CallbackInfo& info) { uint32_t bufsize = getSafeBufSize(this); write_to_jpeg_stream(ensureSurface(), bufsize, &closure); } -#endif char * str_value(Napi::Maybe maybe, const char *fallback, bool can_be_number) { @@ -676,88 +636,6 @@ str_value(Napi::Maybe maybe, const char *fallback, bool can_be_numb return NULL; } -void -Canvas::RegisterFont(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - if (!info[0].IsString()) { - Napi::Error::New(env, "Wrong argument type").ThrowAsJavaScriptException(); - return; - } else if (!info[1].IsObject()) { - Napi::Error::New(env, GENERIC_FACE_ERROR).ThrowAsJavaScriptException(); - return; - } - - std::string filePath = info[0].As(); - PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *)(filePath.c_str())); - - if (!sys_desc) { - Napi::Error::New(env, "Could not parse font file").ThrowAsJavaScriptException(); - return; - } - - PangoFontDescription *user_desc = pango_font_description_new(); - - // now check the attrs, there are many ways to be wrong - Napi::Object js_user_desc = info[1].As(); - - // TODO: use FontParser on these values just like the FontFace API works - char *family = str_value(js_user_desc.Get("family"), NULL, false); - char *weight = str_value(js_user_desc.Get("weight"), "normal", true); - char *style = str_value(js_user_desc.Get("style"), "normal", false); - - if (family && weight && style) { - pango_font_description_set_weight(user_desc, Canvas::GetWeightFromCSSString(weight)); - pango_font_description_set_style(user_desc, Canvas::GetStyleFromCSSString(style)); - pango_font_description_set_family(user_desc, family); - - auto found = std::find_if(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) { - return pango_font_description_equal(f.sys_desc, sys_desc); - }); - - if (found != font_face_list.end()) { - pango_font_description_free(found->user_desc); - found->user_desc = user_desc; - } else if (register_font((unsigned char *) filePath.c_str())) { - FontFace face; - face.user_desc = user_desc; - face.sys_desc = sys_desc; - strncpy((char *)face.file_path, (char *) filePath.c_str(), 1023); - font_face_list.push_back(face); - } else { - pango_font_description_free(user_desc); - Napi::Error::New(env, "Could not load font to the system's font host").ThrowAsJavaScriptException(); - - } - } else { - pango_font_description_free(user_desc); - if (!env.IsExceptionPending()) { - Napi::Error::New(env, GENERIC_FACE_ERROR).ThrowAsJavaScriptException(); - } - } - - free(family); - free(weight); - free(style); - fontSerial++; -} - -void -Canvas::DeregisterAllFonts(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - // Unload all fonts from pango to free up memory - bool success = true; - - std::for_each(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) { - if (!deregister_font( (unsigned char *)f.file_path )) success = false; - pango_font_description_free(f.user_desc); - pango_font_description_free(f.sys_desc); - }); - - font_face_list.clear(); - fontSerial++; - if (!success) Napi::Error::New(env, "Could not deregister one or more fonts").ThrowAsJavaScriptException(); -} - /* * Do not use! This is only exported for testing */ @@ -775,129 +653,23 @@ Canvas::ParseFont(const Napi::CallbackInfo& info) { if (!ok) return env.Undefined(); Napi::Object obj = Napi::Object::New(env); - obj.Set("size", Napi::Number::New(env, props.fontSize)); + obj.Set("size", Napi::Number::New(env, props.size)); Napi::Array families = Napi::Array::New(env); obj.Set("families", families); unsigned int index = 0; - for (auto& family : props.fontFamily) { + for (auto& family : props.families) { families[index++] = Napi::String::New(env, family); } - obj.Set("weight", Napi::Number::New(env, props.fontWeight)); - obj.Set("variant", Napi::Number::New(env, static_cast(props.fontVariant))); - obj.Set("style", Napi::Number::New(env, static_cast(props.fontStyle))); + obj.Set("weight", Napi::Number::New(env, props.weight)); + obj.Set("variant", Napi::Number::New(env, static_cast(props.variant))); + obj.Set("style", Napi::Number::New(env, static_cast(props.style))); return obj; } -/* - * Get a PangoStyle from a CSS string (like "italic") - */ - -PangoStyle -Canvas::GetStyleFromCSSString(const char *style) { - PangoStyle s = PANGO_STYLE_NORMAL; - - if (strlen(style) > 0) { - if (0 == strcmp("italic", style)) { - s = PANGO_STYLE_ITALIC; - } else if (0 == strcmp("oblique", style)) { - s = PANGO_STYLE_OBLIQUE; - } - } - - return s; -} - -/* - * Get a PangoWeight from a CSS string ("bold", "100", etc) - */ - -PangoWeight -Canvas::GetWeightFromCSSString(const char *weight) { - PangoWeight w = PANGO_WEIGHT_NORMAL; - - if (strlen(weight) > 0) { - if (0 == strcmp("bold", weight)) { - w = PANGO_WEIGHT_BOLD; - } else if (0 == strcmp("100", weight)) { - w = PANGO_WEIGHT_THIN; - } else if (0 == strcmp("200", weight)) { - w = PANGO_WEIGHT_ULTRALIGHT; - } else if (0 == strcmp("300", weight)) { - w = PANGO_WEIGHT_LIGHT; - } else if (0 == strcmp("400", weight)) { - w = PANGO_WEIGHT_NORMAL; - } else if (0 == strcmp("500", weight)) { - w = PANGO_WEIGHT_MEDIUM; - } else if (0 == strcmp("600", weight)) { - w = PANGO_WEIGHT_SEMIBOLD; - } else if (0 == strcmp("700", weight)) { - w = PANGO_WEIGHT_BOLD; - } else if (0 == strcmp("800", weight)) { - w = PANGO_WEIGHT_ULTRABOLD; - } else if (0 == strcmp("900", weight)) { - w = PANGO_WEIGHT_HEAVY; - } - } - - return w; -} - -/* - * Given a user description, return a description that will select the - * font either from the system or @font-face - */ - -PangoFontDescription * -Canvas::ResolveFontDescription(const PangoFontDescription *desc) { - // One of the user-specified families could map to multiple SFNT family names - // if someone registered two different fonts under the same family name. - // https://drafts.csswg.org/css-fonts-3/#font-style-matching - FontFace best; - istringstream families(pango_font_description_get_family(desc)); - unordered_set seen_families; - string resolved_families; - bool first = true; - - for (string family; getline(families, family, ','); ) { - string renamed_families; - for (auto& ff : font_face_list) { - string pangofamily = string(pango_font_description_get_family(ff.user_desc)); - if (streq_casein(family, pangofamily)) { - const char* sys_desc_family_name = pango_font_description_get_family(ff.sys_desc); - bool unseen = seen_families.find(sys_desc_family_name) == seen_families.end(); - bool better = best.user_desc == nullptr || pango_font_description_better_match(desc, best.user_desc, ff.user_desc); - - // Avoid sending duplicate SFNT font names due to a bug in Pango for macOS: - // https://bugzilla.gnome.org/show_bug.cgi?id=762873 - if (unseen) { - seen_families.insert(sys_desc_family_name); - - if (better) { - renamed_families = string(sys_desc_family_name) + (renamed_families.size() ? "," : "") + renamed_families; - } else { - renamed_families = renamed_families + (renamed_families.size() ? "," : "") + sys_desc_family_name; - } - } - - if (first && better) best = ff; - } - } - - if (resolved_families.size()) resolved_families += ','; - resolved_families += renamed_families.size() ? renamed_families : family; - first = false; - } - - PangoFontDescription* ret = pango_font_description_copy(best.sys_desc ? best.sys_desc : desc); - pango_font_description_set_family(ret, resolved_families.c_str()); - - return ret; -} - // This returns an approximate value only, suitable for // Napi::MemoryManagement:: AdjustExternalMemory. // The formats that don't map to intrinsic types (RGB30, A1) round up. @@ -907,10 +679,8 @@ Canvas::approxBytesPerPixel() { case CAIRO_FORMAT_ARGB32: case CAIRO_FORMAT_RGB24: return 4; -#ifdef CAIRO_FORMAT_RGB30 case CAIRO_FORMAT_RGB30: return 3; -#endif case CAIRO_FORMAT_RGB16_565: return 2; case CAIRO_FORMAT_A8: diff --git a/src/Canvas.h b/src/Canvas.h index fc7e0ae44..2f81db721 100644 --- a/src/Canvas.h +++ b/src/Canvas.h @@ -7,12 +7,13 @@ struct PdfSvgClosure; #include "closure.h" #include -#include "dll_visibility.h" #include -#include #include #include +#define DLL_PUBLIC __attribute__ ((visibility ("default"))) +#define DLL_LOCAL __attribute__ ((visibility ("hidden"))) + /* * Canvas types. */ @@ -23,18 +24,6 @@ typedef enum { CANVAS_TYPE_SVG } canvas_type_t; - -/* - * FontFace describes a font file in terms of one PangoFontDescription that - * will resolve to it and one that the user describes it as (like @font-face) - */ -class FontFace { - public: - PangoFontDescription *sys_desc = nullptr; - PangoFontDescription *user_desc = nullptr; - unsigned char file_path[1024]; -}; - enum text_baseline_t : uint8_t { TEXT_BASELINE_ALPHABETIC = 0, TEXT_BASELINE_TOP = 1, @@ -77,15 +66,10 @@ class Canvas : public Napi::ObjectWrap { void StreamPNGSync(const Napi::CallbackInfo& info); void StreamPDFSync(const Napi::CallbackInfo& info); void StreamJPEGSync(const Napi::CallbackInfo& info); - static void RegisterFont(const Napi::CallbackInfo& info); - static void DeregisterAllFonts(const Napi::CallbackInfo& info); static Napi::Value ParseFont(const Napi::CallbackInfo& info); Napi::Error CairoError(cairo_status_t status); static void ToPngBufferAsync(Closure* closure); static void ToJpegBufferAsync(Closure* closure); - static PangoWeight GetWeightFromCSSString(const char *weight); - static PangoStyle GetStyleFromCSSString(const char *style); - static PangoFontDescription *ResolveFontDescription(const PangoFontDescription *desc); inline bool isPDF() { return type == CANVAS_TYPE_PDF; } inline bool isSVG() { return type == CANVAS_TYPE_SVG; } @@ -111,7 +95,6 @@ class Canvas : public Napi::ObjectWrap { void destroySurface(); Napi::Env env; - static int fontSerial; private: @@ -119,7 +102,6 @@ class Canvas : public Napi::ObjectWrap { PdfSvgClosure *_closure; Napi::FunctionReference ctor; - static std::vector font_face_list; uint16_t width; uint16_t height; diff --git a/src/CanvasPattern.cc b/src/CanvasPattern.cc index 0d402cc28..9ad5b83e6 100644 --- a/src/CanvasPattern.cc +++ b/src/CanvasPattern.cc @@ -119,7 +119,7 @@ Pattern::createCairoPattern(double alpha, cairo_filter_t patternQuality) { if (canvas) { source_surface = canvas->ensureSurface(); } else if (image) { - source_surface = image->surface(); + source_surface = image->surface.surface(); } if (!source_surface) return nullptr; diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 7b0334913..500fe7d32 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -4,12 +4,16 @@ #include #include +#include #include "Canvas.h" #include "CanvasGradient.h" #include "CanvasPattern.h" +#include "Font.h" #include "InstanceData.h" #include "FontParser.h" +#include "FontLayout.h" #include +#include #include #include "Image.h" #include "ImageData.h" @@ -19,6 +23,7 @@ #include #include "Util.h" #include +#include /* * Rectangle arg assertions. @@ -35,15 +40,6 @@ constexpr double twoPi = M_PI * 2.; -/* - * Simple helper macro for a rather verbose function call. - */ - -#define PANGO_LAYOUT_GET_METRICS(LAYOUT) pango_context_get_metrics( \ - pango_layout_get_context(LAYOUT), \ - pango_layout_get_font_description(LAYOUT), \ - pango_language_from_string(state->lang.c_str())) - inline static bool checkArgs(const Napi::CallbackInfo&info, double *args, int argsNum, int offset = 0){ Napi::Env env = info.Env(); int argsEnd = std::min(9, offset + argsNum); @@ -133,10 +129,8 @@ Context2d::Initialize(Napi::Env& env, Napi::Object& exports) { InstanceMethod<&Context2d::CreatePattern>("createPattern", napi_default_method), InstanceMethod<&Context2d::CreateLinearGradient>("createLinearGradient", napi_default_method), InstanceMethod<&Context2d::CreateRadialGradient>("createRadialGradient", napi_default_method), - #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) InstanceMethod<&Context2d::BeginTag>("beginTag", napi_default_method), InstanceMethod<&Context2d::EndTag>("endTag", napi_default_method), - #endif InstanceAccessor<&Context2d::GetFormat>("pixelFormat", napi_default_jsproperty), InstanceAccessor<&Context2d::GetPatternQuality, &Context2d::SetPatternQuality>("patternQuality", napi_default_jsproperty), InstanceAccessor<&Context2d::GetImageSmoothingEnabled, &Context2d::SetImageSmoothingEnabled>("imageSmoothingEnabled", napi_default_jsproperty), @@ -204,9 +198,7 @@ Context2d::Context2d(const Napi::CallbackInfo& info) : Napi::ObjectWrapcreateCairoContext(); - _layout = pango_cairo_create_layout(_context); - - // As of January 2023, Pango rounds glyph positions which renders text wider - // or narrower than the browser. See #2184 for more information -#if PANGO_VERSION_CHECK(1, 44, 0) - pango_context_set_round_glyph_positions(pango_layout_get_context(_layout), FALSE); -#endif - - pango_layout_set_auto_dir(_layout, FALSE); states.emplace(); state = &states.top(); - pango_layout_set_font_description(_layout, state->fontDescription); } /* @@ -241,7 +223,6 @@ Context2d::Context2d(const Napi::CallbackInfo& info) : Napi::ObjectWrapfontDescription); } /* @@ -276,7 +256,6 @@ Context2d::restore() { cairo_restore(_context); states.pop(); state = &states.top(); - pango_layout_set_font_description(_layout, state->fontDescription); } } @@ -701,9 +680,7 @@ Context2d::GetFormat(const Napi::CallbackInfo& info) { case CAIRO_FORMAT_A8: pixelFormatString = "A8"; break; case CAIRO_FORMAT_A1: pixelFormatString = "A1"; break; case CAIRO_FORMAT_RGB16_565: pixelFormatString = "RGB16_565"; break; -#ifdef CAIRO_FORMAT_RGB30 case CAIRO_FORMAT_RGB30: pixelFormatString = "RGB30"; break; -#endif default: return env.Null(); } return Napi::String::New(env, pixelFormatString); @@ -951,14 +928,12 @@ Context2d::PutImageData(const Napi::CallbackInfo& info) { } break; } -#ifdef CAIRO_FORMAT_RGB30 case CAIRO_FORMAT_RGB30: { // TODO Napi::Error::New(env, "putImageData for CANVAS_FORMAT_RGB30 is not yet implemented").ThrowAsJavaScriptException(); break; } -#endif default: { Napi::Error::New(env, "Invalid pixel format").ThrowAsJavaScriptException(); return; @@ -1160,14 +1135,12 @@ Context2d::GetImageData(const Napi::CallbackInfo& info) { } break; } -#ifdef CAIRO_FORMAT_RGB30 case CAIRO_FORMAT_RGB30: { // TODO Napi::Error::New(env, "getImageData for CANVAS_FORMAT_RGB30 is not yet implemented").ThrowAsJavaScriptException(); break; } -#endif default: { // Unlikely Napi::Error::New(env, "Invalid pixel format").ThrowAsJavaScriptException(); @@ -1292,9 +1265,9 @@ Context2d::DrawImage(const Napi::CallbackInfo& info) { Napi::Error::New(env, "Image given has not completed loading").ThrowAsJavaScriptException(); return; } - source_w = sw = img->width; - source_h = sh = img->height; - surface = img->surface(); + source_w = sw = img->surface.width; + source_h = sh = img->surface.height; + surface = img->surface.surface(); // Canvas } else if (obj.InstanceOf(env.GetInstanceData()->CanvasCtor.Value()).UnwrapOr(false)) { @@ -2439,34 +2412,14 @@ Context2d::Stroke(const Napi::CallbackInfo& info) { */ double -get_text_scale(PangoLayout *layout, double maxWidth) { - - PangoRectangle logical_rect; - pango_layout_get_pixel_extents(layout, NULL, &logical_rect); - - if (logical_rect.width > maxWidth) { - return maxWidth / logical_rect.width; +get_text_scale(TextLayout& layout, double maxWidth) { + if (layout.width > maxWidth) { + return maxWidth / layout.width; } else { return 1.0; } } -/* - * Make sure the layout's font list is up-to-date - */ -void -Context2d::checkFonts() { - // If fonts have been registered, the PangoContext is using an outdated FontMap - if (canvas()->fontSerial != fontSerial) { - pango_context_set_font_map( - pango_layout_get_context(_layout), - pango_cairo_font_map_get_default() - ); - - fontSerial = canvas()->fontSerial; - } -} - void Context2d::paintText(const Napi::CallbackInfo& info, bool stroke) { int argsNum = info.Length() >= 4 ? 3 : 2; @@ -2482,39 +2435,88 @@ Context2d::paintText(const Napi::CallbackInfo& info, bool stroke) { if (!info[0].ToString().UnwrapTo(&strValue)) return; - std::string str = strValue.Utf8Value(); - double x = args[0]; - double y = args[1]; - double scaled_by = 1; + napi_get_value_string_utf16(env, strValue, textBuffer, sizeof(textBuffer), &textLength); - PangoLayout *layout = this->layout(); + InstanceData* data = env.GetInstanceData(); - checkFonts(); - pango_layout_set_text(layout, str.c_str(), -1); - if (state->lang != "") { - pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(state->lang.c_str())); - } - pango_cairo_update_layout(context(), layout); + constexpr size_t CR_GLYPH_BUF_SIZE = 1024; + cairo_glyph_t glyphs[CR_GLYPH_BUF_SIZE]; - PangoDirection pango_dir = state->direction == "ltr" ? PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL; - pango_context_set_base_dir(pango_layout_get_context(_layout), pango_dir); + TextLayout layout = layoutText(textBuffer, textLength, state, data); + double x = args[0] - layout.anchorX; + double y = args[1] + layout.anchorY; if (argsNum == 3) { if (args[2] <= 0) return; - scaled_by = get_text_scale(layout, args[2]); + double scaled_by = get_text_scale(layout, args[2]); cairo_save(context()); cairo_scale(context(), scaled_by, 1); } - savePath(); - if (state->textDrawingMode == TEXT_DRAW_GLYPHS) { - if (stroke == true) { this->stroke(); } else { this->fill(); } - setTextPath(x / scaled_by, y); - } else if (state->textDrawingMode == TEXT_DRAW_PATHS) { - setTextPath(x / scaled_by, y); - if (stroke == true) { this->stroke(); } else { this->fill(); } + for (GlyphRun& run : layout.runs) { + double fontSize = state->fontProperties.size; + double toPx = fontSize / 1000; + + FT_Face ftface; + FT_Error newFaceResult = FT_New_Memory_Face( + data->ft, + reinterpret_cast(run.face->data.get()), + run.face->data_len, + run.face->index, + &ftface + ); + + if (newFaceResult != 0) continue; + + cairo_font_face_t* crface = cairo_ft_font_face_create_for_ft_face(ftface, 0); + + static const cairo_user_data_key_t key{0}; + cairo_status_t setDataResult = cairo_font_face_set_user_data( + crface, + &key, + ftface, + (cairo_destroy_func_t) FT_Done_Face + ); + + if (setDataResult) { + cairo_font_face_destroy(crface); + FT_Done_Face (ftface); + continue; + } + + cairo_set_font_face(context(), crface); + cairo_set_font_size(context(), fontSize); + + size_t hbGlyphIndex = 0; + while (hbGlyphIndex < run.glyphs.size()) { + size_t crGlyphIndex = 0; + + while (crGlyphIndex < CR_GLYPH_BUF_SIZE && hbGlyphIndex < run.glyphs.size()) { + glyphs[crGlyphIndex].index = run.glyphs[hbGlyphIndex].id; + glyphs[crGlyphIndex].x = x + run.glyphs[hbGlyphIndex].x_offset * toPx; + glyphs[crGlyphIndex].y = y + run.glyphs[hbGlyphIndex].y_offset * toPx; + x += run.glyphs[hbGlyphIndex].x_advance * toPx; + y += run.glyphs[hbGlyphIndex].y_advance * toPx; + + hbGlyphIndex += 1; + crGlyphIndex += 1; + } + + savePath(); + if (state->textDrawingMode == TEXT_DRAW_GLYPHS) { + if (stroke) { this->stroke(); } else { this->fill(); } + setTextPath(glyphs, crGlyphIndex); + } else if (state->textDrawingMode == TEXT_DRAW_PATHS) { + setTextPath(glyphs, crGlyphIndex); + if (stroke) { this->stroke(); } else { this->fill(); } + } + restorePath(); + } + + cairo_set_font_face(context(), nullptr); + cairo_font_face_destroy(crface); } - restorePath(); + if (argsNum == 3) { cairo_restore(context()); } @@ -2538,75 +2540,13 @@ Context2d::StrokeText(const Napi::CallbackInfo& info) { paintText(info, true); } -/* - * Gets the baseline adjustment in device pixels - */ -inline double getBaselineAdjustment(PangoLayout* layout, short baseline) { - PangoRectangle logical_rect; - pango_layout_line_get_extents(pango_layout_get_line(layout, 0), NULL, &logical_rect); - - double scale = 1.0 / PANGO_SCALE; - double ascent = scale * pango_layout_get_baseline(layout); - double descent = scale * logical_rect.height - ascent; - - switch (baseline) { - case TEXT_BASELINE_ALPHABETIC: - return ascent; - case TEXT_BASELINE_MIDDLE: - return (ascent + descent) / 2.0; - case TEXT_BASELINE_BOTTOM: - return ascent + descent; - default: - return 0; - } -} - -text_align_t -Context2d::resolveTextAlignment() { - text_align_t alignment = state->textAlignment; - - // Convert start/end to left/right based on direction - if (alignment == TEXT_ALIGNMENT_START) { - return (state->direction == "rtl") ? TEXT_ALIGNMENT_RIGHT : TEXT_ALIGNMENT_LEFT; - } else if (alignment == TEXT_ALIGNMENT_END) { - return (state->direction == "rtl") ? TEXT_ALIGNMENT_LEFT : TEXT_ALIGNMENT_RIGHT; - } - - return alignment; -} - -/* - * Set text path for the string in the layout at (x, y). - * This function is called by paintText and won't behave correctly - * if is not called from there. - * it needs pango_layout_set_text and pango_cairo_update_layout to be called before - */ - void -Context2d::setTextPath(double x, double y) { - PangoRectangle logical_rect; - text_align_t alignment = resolveTextAlignment(); - - switch (alignment) { - case TEXT_ALIGNMENT_CENTER: - pango_layout_get_pixel_extents(_layout, NULL, &logical_rect); - x -= logical_rect.width / 2; - break; - case TEXT_ALIGNMENT_RIGHT: - pango_layout_get_pixel_extents(_layout, NULL, &logical_rect); - x -= logical_rect.width; - break; - default: // TEXT_ALIGNMENT_LEFT - break; - } - - y -= getBaselineAdjustment(_layout, state->textBaseline); - - cairo_move_to(_context, x, y); - if (state->textDrawingMode == TEXT_DRAW_PATHS) { - pango_cairo_layout_path(_context, _layout); - } else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) { - pango_cairo_show_layout(_context, _layout); +Context2d::setTextPath(cairo_glyph_t* glyphs, size_t numGlyphs) { + if (state->textDrawingMode == TEXT_DRAW_GLYPHS) { + // TODO: use cairo_show_text_glyphs ! + cairo_show_glyphs(context(), glyphs, numGlyphs); + } else { + cairo_glyph_path(context(), glyphs, numGlyphs); } } @@ -2665,40 +2605,8 @@ Context2d::SetFont(const Napi::CallbackInfo& info, const Napi::Value& value) { auto props = FontParser::parse(str, &success); if (!success) return; - PangoFontDescription *desc = pango_font_description_copy(state->fontDescription); - pango_font_description_free(state->fontDescription); - - PangoStyle style = props.fontStyle == FontStyle::Italic ? PANGO_STYLE_ITALIC - : props.fontStyle == FontStyle::Oblique ? PANGO_STYLE_OBLIQUE - : PANGO_STYLE_NORMAL; - pango_font_description_set_style(desc, style); - - pango_font_description_set_weight(desc, static_cast(props.fontWeight)); - - std::string family = props.fontFamily.empty() ? "" : props.fontFamily[0]; - for (size_t i = 1; i < props.fontFamily.size(); i++) { - family += "," + props.fontFamily[i]; - } - if (family.length() > 0) { - // See #1643 - Pango understands "sans" whereas CSS uses "sans-serif" - std::string s1(family); - std::string s2("sans-serif"); - if (streq_casein(s1, s2)) { - pango_font_description_set_family(desc, "sans"); - } else { - pango_font_description_set_family(desc, family.c_str()); - } - } - - PangoFontDescription *sys_desc = Canvas::ResolveFontDescription(desc); - pango_font_description_free(desc); - - if (props.fontSize > 0) pango_font_description_set_absolute_size(sys_desc, props.fontSize * PANGO_SCALE); - - state->fontDescription = sys_desc; - pango_layout_set_font_description(_layout, sys_desc); - state->font = str; + state->fontProperties = props; } /* @@ -2796,67 +2704,53 @@ Context2d::MeasureText(const Napi::CallbackInfo& info) { Napi::String str; if (!info[0].ToString().UnwrapTo(&str)) return env.Undefined(); + napi_get_value_string_utf16(env, str, textBuffer, sizeof(textBuffer), &textLength); Napi::Object obj = Napi::Object::New(env); + InstanceData* data = env.GetInstanceData(); - PangoRectangle _ink_rect, _logical_rect; - float_rectangle ink_rect, logical_rect; - PangoFontMetrics *metrics; - PangoLayout *layout = this->layout(); - - checkFonts(); - pango_layout_set_text(layout, str.Utf8Value().c_str(), -1); - if (state->lang != "") { - pango_context_set_language(pango_layout_get_context(_layout), pango_language_from_string(state->lang.c_str())); - } - pango_cairo_update_layout(ctx, layout); - - // Normally you could use pango_layout_get_pixel_extents and be done, or use - // pango_extents_to_pixels, but both of those round the pixels, so we have to - // divide by PANGO_SCALE manually - pango_layout_get_extents(layout, &_ink_rect, &_logical_rect); - - float inverse_pango_scale = 1. / PANGO_SCALE; - - logical_rect.x = _logical_rect.x * inverse_pango_scale; - logical_rect.y = _logical_rect.y * inverse_pango_scale; - logical_rect.width = _logical_rect.width * inverse_pango_scale; - logical_rect.height = _logical_rect.height * inverse_pango_scale; - - ink_rect.x = _ink_rect.x * inverse_pango_scale; - ink_rect.y = _ink_rect.y * inverse_pango_scale; - ink_rect.width = _ink_rect.width * inverse_pango_scale; - ink_rect.height = _ink_rect.height * inverse_pango_scale; - - metrics = PANGO_LAYOUT_GET_METRICS(layout); - - text_align_t alignment = resolveTextAlignment(); - - double x_offset; - switch (alignment) { - case TEXT_ALIGNMENT_CENTER: - x_offset = logical_rect.width / 2.; - break; - case TEXT_ALIGNMENT_RIGHT: - x_offset = logical_rect.width; - break; - case TEXT_ALIGNMENT_LEFT: - default: - x_offset = 0.0; + TextLayout layout = layoutText(textBuffer, textLength, state, data); + + double fontSize = state->fontProperties.size; + double ax = 0; + double ay = 0; + double actualBoundingBoxLeft = 0; + double actualBoundingBoxRight = 0; + double actualBoundingBoxAscent = 0; + double actualBoundingBoxDescent = 0; + bool first = true; + + // first calculate this bounding box, with no anchor: + // - ascent and descent increasing means going away from baseline + // - left and right increasing means going right + for (GlyphRun& run : layout.runs) { + double toPx = fontSize / 1000; + for (Glyph& glyph : run.glyphs) { + actualBoundingBoxLeft = std::min(actualBoundingBoxLeft, ax + glyph.x_bearing * toPx); + actualBoundingBoxAscent = std::max(actualBoundingBoxAscent, ay + glyph.y_bearing * toPx); + actualBoundingBoxRight = std::max(actualBoundingBoxRight, ax + glyph.x_bearing * toPx + glyph.width * toPx); + actualBoundingBoxDescent = std::max(actualBoundingBoxDescent, ay - glyph.y_bearing * toPx - glyph.height * toPx); + ax += glyph.x_advance * toPx; + ay += glyph.y_advance * toPx; + } } - double y_offset = getBaselineAdjustment(layout, state->textBaseline); - - obj.Set("width", Napi::Number::New(env, logical_rect.width)); - obj.Set("actualBoundingBoxLeft", Napi::Number::New(env, PANGO_LBEARING(ink_rect) + x_offset)); - obj.Set("actualBoundingBoxRight", Napi::Number::New(env, PANGO_RBEARING(ink_rect) - x_offset)); - obj.Set("actualBoundingBoxAscent", Napi::Number::New(env, y_offset + PANGO_ASCENT(ink_rect))); - obj.Set("actualBoundingBoxDescent", Napi::Number::New(env, PANGO_DESCENT(ink_rect) - y_offset)); - obj.Set("emHeightAscent", Napi::Number::New(env, -(PANGO_ASCENT(logical_rect) - y_offset))); - obj.Set("emHeightDescent", Napi::Number::New(env, PANGO_DESCENT(logical_rect) - y_offset)); - obj.Set("alphabeticBaseline", Napi::Number::New(env, -(pango_font_metrics_get_ascent(metrics) * inverse_pango_scale - y_offset))); + // convert to whatwg-specified box with an anchor: + // - ascent and descent increasing means going from anchor outwards + // - left and right increasing means going from anchor outwards + actualBoundingBoxLeft = layout.anchorX - actualBoundingBoxLeft; + actualBoundingBoxRight = actualBoundingBoxRight - layout.anchorX; + actualBoundingBoxAscent = actualBoundingBoxAscent - layout.anchorY; + actualBoundingBoxDescent = layout.anchorY + actualBoundingBoxDescent; - pango_font_metrics_unref(metrics); + obj.Set("width", Napi::Number::New(env, actualBoundingBoxLeft + actualBoundingBoxRight)); + obj.Set("actualBoundingBoxLeft", Napi::Number::New(env, actualBoundingBoxLeft)); + obj.Set("actualBoundingBoxRight", Napi::Number::New(env, actualBoundingBoxRight)); + obj.Set("actualBoundingBoxAscent", Napi::Number::New(env, actualBoundingBoxAscent)); + obj.Set("actualBoundingBoxDescent", Napi::Number::New(env, actualBoundingBoxDescent)); + obj.Set("emHeightAscent", Napi::Number::New(env, layout.anchorY + layout.metrics.central + fontSize / 2)); + obj.Set("emHeightDescent", Napi::Number::New(env, layout.anchorY + layout.metrics.central - fontSize / 2)); + obj.Set("alphabeticBaseline", Napi::Number::New(env, -layout.anchorY)); return obj; } @@ -3432,8 +3326,6 @@ Context2d::Ellipse(const Napi::CallbackInfo& info) { cairo_set_matrix(ctx, &save_matrix); } -#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) - void Context2d::BeginTag(const Napi::CallbackInfo& info) { std::string tagName = ""; @@ -3479,5 +3371,3 @@ Context2d::EndTag(const Napi::CallbackInfo& info) { cairo_tag_end(_context, tagName.c_str()); } - -#endif diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index 63caa021e..946b1f2bc 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -4,9 +4,11 @@ #include "cairo.h" #include "Canvas.h" +#include "Font.h" +#include "FontLayout.h" #include "color.h" #include "napi.h" -#include +#include "unicode.h" #include /* @@ -26,7 +28,7 @@ struct canvas_state_t { Napi::ObjectReference strokePattern; Napi::ObjectReference fillGradient; Napi::ObjectReference strokeGradient; - PangoFontDescription* fontDescription = nullptr; + FontProperties fontProperties; std::string font = "10px sans-serif"; cairo_filter_t patternQuality = CAIRO_FILTER_GOOD; float globalAlpha = 1.f; @@ -38,10 +40,7 @@ struct canvas_state_t { std::string direction = "ltr"; std::string lang = ""; - canvas_state_t() { - fontDescription = pango_font_description_from_string("sans"); - pango_font_description_set_absolute_size(fontDescription, 10 * PANGO_SCALE); - } + canvas_state_t() {} canvas_state_t(const canvas_state_t& other) { fill = other.fill; @@ -59,16 +58,12 @@ struct canvas_state_t { shadowOffsetX = other.shadowOffsetX; shadowOffsetY = other.shadowOffsetY; textDrawingMode = other.textDrawingMode; - fontDescription = pango_font_description_copy(other.fontDescription); + fontProperties = other.fontProperties; font = other.font; imageSmoothingEnabled = other.imageSmoothingEnabled; direction = other.direction; lang = other.lang; } - - ~canvas_state_t() { - pango_font_description_free(fontDescription); - } }; /* @@ -111,7 +106,6 @@ class Context2d : public Napi::ObjectWrap { void Stroke(const Napi::CallbackInfo& info); void FillText(const Napi::CallbackInfo& info); void StrokeText(const Napi::CallbackInfo& info); - static Napi::Value SetFont(const Napi::CallbackInfo& info); void SetLineDash(const Napi::CallbackInfo& info); Napi::Value GetLineDash(const Napi::CallbackInfo& info); Napi::Value MeasureText(const Napi::CallbackInfo& info); @@ -179,10 +173,8 @@ class Context2d : public Napi::ObjectWrap { void SetTextBaseline(const Napi::CallbackInfo& info, const Napi::Value& value); void SetTextAlign(const Napi::CallbackInfo& info, const Napi::Value& value); void SetLanguage(const Napi::CallbackInfo& info, const Napi::Value& value); - #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) void BeginTag(const Napi::CallbackInfo& info); void EndTag(const Napi::CallbackInfo& info); - #endif Napi::Value GetDirection(const Napi::CallbackInfo& info); void SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value); inline void setContext(cairo_t *ctx) { _context = ctx; } @@ -191,7 +183,7 @@ class Context2d : public Napi::ObjectWrap { inline bool hasShadow(); void inline setSourceRGBA(rgba_t color); void inline setSourceRGBA(cairo_t *ctx, rgba_t color); - void setTextPath(double x, double y); + void setTextPath(cairo_glyph_t* glyphs, size_t numGlyphs); void blur(cairo_surface_t *surface, int radius); void shadow(void (fn)(cairo_t *cr)); void savePath(); @@ -202,7 +194,6 @@ class Context2d : public Napi::ObjectWrap { void save(); void restore(); void resetState(); - inline PangoLayout *layout(){ return _layout; } ~Context2d(); Napi::Env env; @@ -212,12 +203,12 @@ class Context2d : public Napi::ObjectWrap { Napi::Value get_current_transform(); void _setFillColor(Napi::Value arg); void _setStrokeColor(Napi::Value arg); - void checkFonts(); void paintText(const Napi::CallbackInfo&, bool); text_align_t resolveTextAlignment(); Canvas *_canvas; cairo_t *_context = nullptr; cairo_path_t *_path; - PangoLayout *_layout = nullptr; - int fontSerial = 1; + // 65536 is an arbitrary max fillText length. Use sizeof to get the capacity. + char16_t textBuffer[65536]; + size_t textLength; }; diff --git a/src/Font.h b/src/Font.h new file mode 100644 index 000000000..0263943f0 --- /dev/null +++ b/src/Font.h @@ -0,0 +1,96 @@ +// Copyright (c) 2024 Caleb Hearon +// Stuff common to all perspectives on fonts: CSS, OS fonts, querying, etc. +#pragma once + +#include +#include +#include + +enum class FontStyle { + Normal, + Italic, + Oblique +}; + +enum class FontVariant { + Normal, + SmallCaps +}; + +enum class FontStatus { Unloaded, Loaded, Error }; + +#ifdef _WIN32 +using file_char = wchar_t; +#else +using file_char = char; +#endif + +// Descriptors and properties (see next comments) +struct FontBase { + uint16_t weight{400}; + FontVariant variant{FontVariant::Normal}; + FontStyle style{FontStyle::Normal}; + uint16_t stretch{100}; +}; + +// Descriptors describe real fonts on the OS +struct FontDescriptor : FontBase { + std::unique_ptr family; + // The postscript name is kept purely to select fonts within a TrueType + // Collection (ttc). macOS CTFontDescriptors don't tell you which index + // they represent in a ttc, so when we begin to draw with a ttc match, we + // select which index to draw with based on which one has this name. + std::unique_ptr postscript = nullptr; + std::unique_ptr url = nullptr; + std::unique_ptr data = nullptr; + size_t data_len = 0; + size_t index = 0; + FontStatus status = FontStatus::Unloaded; + + void load() { + if (data != nullptr || url == nullptr) return; + + FILE* file = nullptr; + long file_size = 0; + std::unique_ptr buffer; + + // TODO: propagate error +#ifdef _WIN32 + file = _wfopen(url.get(), L"rb"); +#else + file = fopen(url.get(), "rb"); +#endif + if (!file) goto bail; + + // TODO: propagate error + if (fseek(file, 0, SEEK_END) != 0) goto bail; + + // TODO: propagate error + file_size = ftell(file); + if (file_size < 0) goto bail; + + // TODO: propagate error + if (fseek(file, 0, SEEK_SET) != 0) goto bail; + + buffer = std::make_unique(file_size); + if (fread(buffer.get(), 1, file_size, file) != static_cast(file_size)) { + goto bail; + } + + data = std::move(buffer); + data_len = file_size; + status = FontStatus::Loaded; + fclose(file); + return; + +bail: + status = FontStatus::Error; + if (file) fclose(file); + } +}; + +// Properties describe desired fonts from CSS/ctx.font +struct FontProperties : FontBase { + std::vector families; + double size{10.0f}; +}; diff --git a/src/FontFace.cc b/src/FontFace.cc new file mode 100644 index 000000000..e0e0110df --- /dev/null +++ b/src/FontFace.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2024 Caleb Hearon + +#include "FontFace.h" +#include "FontParser.h" +#include "InstanceData.h" + +#include +#include + +static size_t lastId = 0; + +FontFace::FontFace(const Napi::CallbackInfo& info) : + Napi::ObjectWrap(info), + id(++lastId), + env(info.Env()) +{ + if (info.Length() < 2) { + Napi::TypeError::New(env, "Family and source arguments are required").ThrowAsJavaScriptException(); + return; + } + + if (!setFamilyInternal(info[0])) return; + + if (info[1].IsString()) { + Napi::String url = info[1].As(); + size_t len; +#ifdef _WIN32 + napi_get_value_string_utf16(env, url, nullptr, 0, &len); +#else + napi_get_value_string_utf8(env, url, nullptr, 0, &len); +#endif + len++; + descriptor.url = std::make_unique(len); +#ifdef _WIN32 + napi_get_value_string_utf16(env, url, (char16_t*)descriptor.url.get(), len, nullptr); +#else + napi_get_value_string_utf8(env, url, descriptor.url.get(), len, nullptr); +#endif + } else if (info[1].IsTypedArray()) { + Napi::TypedArray ta = info[1].As(); + Napi::ArrayBuffer buf = ta.ArrayBuffer(); + descriptor.data_len = ta.ByteLength(); + descriptor.data = std::make_unique(descriptor.data_len); + std::memcpy(descriptor.data.get(), static_cast(buf.Data()) + ta.ByteOffset(), descriptor.data_len); + } else if (info[1].IsArrayBuffer()) { + Napi::ArrayBuffer buf = info[1].As(); + descriptor.data_len = buf.ByteLength(); + descriptor.data = std::make_unique(descriptor.data_len); + std::memcpy(descriptor.data.get(), buf.Data(), descriptor.data_len); + } else { + Napi::TypeError::New(env, "Source must be a string or buffer").ThrowAsJavaScriptException(); + return; + } + + if (info.Length() >= 3) { + if (!info[2].IsObject()) { + Napi::TypeError::New(env, "Descriptors must be an object").ThrowAsJavaScriptException(); + return; + } + Napi::Object descriptors = info[2].As(); + Napi::Value value; + if (descriptors.Has("weight").UnwrapOr(false) && descriptors.Get("weight").UnwrapTo(&value)) { + if (!setWeightInternal(value)) return; + } + if (descriptors.Has("style").UnwrapOr(false) && descriptors.Get("style").UnwrapTo(&value)) { + if (!setStyleInternal(value)) return; + } + } +} + +void +FontFace::Initialize(Napi::Env& env, Napi::Object& exports) { + InstanceData *data = env.GetInstanceData(); + Napi::Function ctor = DefineClass(env, "FontFace", { + InstanceAccessor<&FontFace::GetFamily, &FontFace::SetFamily>("family", napi_default_jsproperty), + InstanceAccessor<&FontFace::GetStyle, &FontFace::SetStyle>("style", napi_default_jsproperty), + InstanceAccessor<&FontFace::GetWeight, &FontFace::SetWeight>("weight", napi_default_jsproperty), + InstanceAccessor<&FontFace::GetStatus>("status", napi_default_jsproperty) + }); + + data->FontFaceCtor = Napi::Persistent(ctor); + exports.Set("FontFace", ctor); +} + +Napi::Value +FontFace::GetFamily(const Napi::CallbackInfo& info) { + return Napi::String::New(env, descriptor.family.get()); +} + +bool +FontFace::setFamilyInternal(const Napi::Value& value) { + // According to the specs, this is supposed to go through the CSS parser, so + // fonts starting with numbers or having special characters should actually + // throw an error. However, Safari accepts anything, and Firefox puts quotes + // around the family: https://bugzilla.mozilla.org/show_bug.cgi?id=1986533 + if (Napi::String family; value.ToString().UnwrapTo(&family)) { + size_t len; + napi_get_value_string_utf8(env, family, nullptr, 0, &len); + len++; + descriptor.family = std::make_unique(len); + napi_get_value_string_utf8(env, family, descriptor.family.get(), len, nullptr); + return true; + } + return false; +} + +void +FontFace::SetFamily(const Napi::CallbackInfo& info, const Napi::Value& value) { + setFamilyInternal(value); +} + +Napi::Value +FontFace::GetStyle(const Napi::CallbackInfo& info) { + switch (descriptor.style) { + case FontStyle::Normal: return Napi::String::New(env, "normal"); + case FontStyle::Italic: return Napi::String::New(env, "italic"); + case FontStyle::Oblique: return Napi::String::New(env, "oblique"); + } +} + +bool +FontFace::setStyleInternal(const Napi::Value& value) { + if (Napi::String style; value.ToString().UnwrapTo(&style)) { + if (auto value = FontParser::parseStyle(style.Utf8Value()); value) { + descriptor.style = *value; + return true; + } else { + Napi::TypeError::New(env, "Could not parse style").ThrowAsJavaScriptException(); + } + } + return false; +} + +void +FontFace::SetStyle(const Napi::CallbackInfo& info, const Napi::Value& value) { + setStyleInternal(value); +} + +Napi::Value +FontFace::GetWeight(const Napi::CallbackInfo& info) { + return Napi::String::New(env, std::to_string(descriptor.weight)); +} + +bool +FontFace::setWeightInternal(const Napi::Value& value) { + if (Napi::String weight; value.ToString().UnwrapTo(&weight)) { + if (auto value = FontParser::parseWeight(weight.Utf8Value()); value) { + descriptor.weight = *value; + return true; + } else { + Napi::TypeError::New(env, "Could not parse weight").ThrowAsJavaScriptException(); + } + } + return false; +} + +void +FontFace::SetWeight(const Napi::CallbackInfo& info, const Napi::Value& value) { + setWeightInternal(value); +} + +Napi::Value +FontFace::GetStatus(const Napi::CallbackInfo& info) { + switch (descriptor.status) { + case FontStatus::Unloaded: return Napi::String::New(env, "unloaded"); + case FontStatus::Loaded: return Napi::String::New(env, "loaded"); + case FontStatus::Error: return Napi::String::New(env, "error"); + } +} diff --git a/src/FontFace.h b/src/FontFace.h new file mode 100644 index 000000000..779101159 --- /dev/null +++ b/src/FontFace.h @@ -0,0 +1,32 @@ +// Copyright (c) 2024 Caleb Hearon +// +// TODO: ttc/otc with fragment identifier + +#pragma once + +#include +#include "Font.h" + +class FontFace : public Napi::ObjectWrap { + public: + FontFace(const Napi::CallbackInfo& info); + static void Initialize(Napi::Env& env, Napi::Object& target); + + Napi::Value GetFamily(const Napi::CallbackInfo& info); + Napi::Value GetStyle(const Napi::CallbackInfo& info); + Napi::Value GetWeight(const Napi::CallbackInfo& info); + Napi::Value GetStatus(const Napi::CallbackInfo& info); + bool setFamilyInternal(const Napi::Value& value); + void SetFamily(const Napi::CallbackInfo& info, const Napi::Value& value); + bool setStyleInternal(const Napi::Value& value); + void SetStyle(const Napi::CallbackInfo& info, const Napi::Value& value); + bool setWeightInternal(const Napi::Value& value); + void SetWeight(const Napi::CallbackInfo& info, const Napi::Value& value); + + size_t id; + + //TODO private + FontDescriptor descriptor; + private: + Napi::Env env; +}; diff --git a/src/FontFaceSet.cc b/src/FontFaceSet.cc new file mode 100644 index 000000000..4c06e70c4 --- /dev/null +++ b/src/FontFaceSet.cc @@ -0,0 +1,192 @@ +// Copyright (c) 2024 Caleb Hearon + +#include + +#include "FontFaceSet.h" +#include "InstanceData.h" + +FontFaceSet::FontFaceSet(Napi::CallbackInfo& info) : + env(info.Env()), + ready(Napi::Promise::Deferred::New(info.Env())), + Napi::ObjectWrap(info) { +} + +void +FontFaceSet::Initialize(Napi::Env& env, Napi::Object& exports) { + InstanceData *data = env.GetInstanceData(); + + Napi::Symbol iteratorSymbol = Napi::Symbol::WellKnown(env, "iterator").Unwrap(); + + Napi::Function ctor = DefineClass(env, "FontFaceSet", { + InstanceMethod<&FontFaceSet::Add>("add", napi_default_method), + InstanceMethod<&FontFaceSet::Has>("has", napi_default_method), + InstanceMethod<&FontFaceSet::Clear>("clear", napi_default_method), + InstanceMethod<&FontFaceSet::Delete>("delete", napi_default_method), + InstanceMethod<&FontFaceSet::Iterator>(iteratorSymbol, napi_default_jsproperty), + InstanceAccessor<&FontFaceSet::Size>("size", napi_default_jsproperty) + }); + + Napi::Object jsFonts = ctor.New({}).Unwrap(); + FontFaceSet* cppFonts = FontFaceSet::Unwrap(jsFonts); + + // FontFaceSet is a singleton. 2/3 browsers do not allow you to construct + // FontFaceSet, against specs, which allow you to use them to load groups. + data->cppFontSet = cppFonts; + data->jsFontSet = Napi::Persistent(jsFonts); + exports.Set("fonts", jsFonts); +} + +Napi::Value +FontFaceSet::Add(const Napi::CallbackInfo& info) { + InstanceData *data = env.GetInstanceData(); + + if (info.Length() == 0) { + Napi::TypeError::New(env, "face argument is required").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + bool isFontFace; + Napi::Object obj; + + if (!info[0].IsObject()) { + isFontFace = false; + } else { + obj = info[0].As(); + if (!obj.InstanceOf(data->FontFaceCtor.Value()).UnwrapTo(&isFontFace)) return env.Undefined(); + } + if (!isFontFace) { + Napi::TypeError::New(env, "Expected instance of FontFace").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + FontFace* face = FontFace::Unwrap(obj); + + if (!facesHash.contains(face->id)) { + facesHash.insert({face->id, facesData.size()}); + facesData.push_back({Napi::Persistent(obj), face}); + } + + return info.This(); // TODO not technically right +} + +Napi::Value +FontFaceSet::Has(const Napi::CallbackInfo& info) { + InstanceData *data = env.GetInstanceData(); + + if (info.Length() == 0) { + Napi::TypeError::New(env, "face argument is required").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + bool isFontFace; + Napi::Object obj; + + if (!info[0].IsObject()) { + isFontFace = false; + } else { + obj = info[0].As(); + if (!obj.InstanceOf(data->FontFaceCtor.Value()).UnwrapTo(&isFontFace)) return env.Undefined(); + } + if (!isFontFace) { + Napi::TypeError::New(env, "Expected instance of FontFace").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + FontFace* face = FontFace::Unwrap(obj); + + return facesHash.find(face->id) != facesHash.end() + ? Napi::Boolean::New(env, true) + : Napi::Boolean::New(env, false); +} + +void +FontFaceSet::Clear(const Napi::CallbackInfo& info) { + for (auto& entry : facesData) { + entry.face = nullptr; + entry.ref.Reset(); + } + facesHash.clear(); +} + +Napi::Value +FontFaceSet::Delete(const Napi::CallbackInfo& info) { + InstanceData *data = env.GetInstanceData(); + + if (info.Length() == 0) { + Napi::TypeError::New(env, "face argument is required").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + bool isFontFace; + Napi::Object obj; + + if (!info[0].IsObject()) { + isFontFace = false; + } else { + obj = info[0].As(); + if (!obj.InstanceOf(data->FontFaceCtor.Value()).UnwrapTo(&isFontFace)) return env.Undefined(); + } + if (!isFontFace) { + Napi::TypeError::New(env, "Expected instance of FontFace").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + FontFace* face = FontFace::Unwrap(obj); + + if (auto it = facesHash.find(face->id); it != facesHash.end()) { + facesData[it->second].face = nullptr; + facesData[it->second].ref.Reset(); + facesHash.erase(it); + loaded.erase(face->id); + failed.erase(face->id); + return Napi::Boolean::New(env, true); + } else { + return Napi::Boolean::New(env, false); + } +} + +struct IteratorContext { + // There is only ever one FontFaceSet so this should always be valid + FontFaceSet* set; + size_t index; + + static void finalize(Napi::Env env, IteratorContext* ctx) { + delete ctx; + } + + static Napi::Value next(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + IteratorContext* ctx = static_cast(info.Data()); + FontFaceSet& set = *(ctx->set); + + Napi::Object ret = Napi::Object::New(env); + + // Skip past empty slots + for (size_t i = ctx->index; i < set.facesData.size(); i++) { + auto& entry = set.facesData[ctx->index++]; + if (entry.face != nullptr) { + ret.Set("done", Napi::Boolean::New(env, false)); + ret.Set("value", entry.ref.Value()); + return ret; + } + } + + ret.Set("done", Napi::Boolean::New(env, true)); + return ret; + } +}; + +Napi::Value +FontFaceSet::Iterator(const Napi::CallbackInfo& info) { + Napi::Object iterator = Napi::Object::New(env); + IteratorContext* ctx = new IteratorContext{this, 0}; + iterator.AddFinalizer(IteratorContext::finalize, ctx); + Napi::Function next = Napi::Function::New(env, IteratorContext::next, "next", ctx); + iterator["next"] = next; + return iterator; +} + +Napi::Value +FontFaceSet::Size(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, facesHash.size()); +} diff --git a/src/FontFaceSet.h b/src/FontFaceSet.h new file mode 100644 index 000000000..035ff3ef6 --- /dev/null +++ b/src/FontFaceSet.h @@ -0,0 +1,43 @@ +// Copyright (c) 2024 Caleb Hearon + +#pragma once + +#include +#include +#include + +#include "FontFace.h" + +static bool ref_compare(const FontFace* a, const FontFace* b) { + return a->id < b->id; +} + +struct FontFaceSetEntry { + Napi::ObjectReference ref; + FontFace* face; +}; + +class FontFaceSet : public Napi::ObjectWrap { + public: + FontFaceSet(Napi::CallbackInfo& info); + static void Initialize(Napi::Env& env, Napi::Object& exports); + + Napi::Value Add(const Napi::CallbackInfo& info); + Napi::Value Has(const Napi::CallbackInfo& info); + void Clear(const Napi::CallbackInfo& info); + Napi::Value Delete(const Napi::CallbackInfo& info); + Napi::Value Iterator(const Napi::CallbackInfo& info); + Napi::Value Size(const Napi::CallbackInfo& info); + + // Iteration of faces must be safe. We'll achieve this by iterating all keys + // ever inserted, which is how the EcmaScript standards say to iterate Sets. + std::unordered_map facesHash; + std::vector facesData; + + private: + std::set loading; + std::set loaded; + std::set failed; + Napi::Promise::Deferred ready; + Napi::Env env; +}; diff --git a/src/FontLayout.cc b/src/FontLayout.cc new file mode 100644 index 000000000..c2b02a020 --- /dev/null +++ b/src/FontLayout.cc @@ -0,0 +1,608 @@ +// Copyright (c) 2024 Caleb Hearon + +#include + +#include "FontLayout.h" +#include "Util.h" + +// Gets a code point from a UTF-16 string +// handling surrogate pairs appropriately +uint32_t +codePointAt(char16_t* str, size_t len, size_t* idx) { + uint16_t hi, low; + uint16_t code = str[(*idx)++]; + + // High surrogate + if (0xd800 <= code && code <= 0xdbff) { + hi = code; + if (*idx < len) { + low = str[(*idx)++]; + if (0xdc00 <= low && low <= 0xdfff) { + return ((hi - 0xd800) * 0x400) + (low - 0xdc00) + 0x10000; + } + } + return hi; + } + + return code; +} + +bool +compareGlyphRunTextPosition(const GlyphRun& a, const GlyphRun& b) { + return a.start < b.start; +} + +/* + * Gets the baseline adjustment in device pixels + */ +double getBaselineAdjustment(text_baseline_t baseline, FontMetrics metrics, double fontSize) { + switch (baseline) { + case TEXT_BASELINE_ALPHABETIC: + return 0; + case TEXT_BASELINE_TOP: + return metrics.central + fontSize / 2; + case TEXT_BASELINE_MIDDLE: + return metrics.central; + case TEXT_BASELINE_BOTTOM: + return metrics.central - fontSize / 2; + case TEXT_BASELINE_IDEOGRAPHIC: + return metrics.ideographic; + case TEXT_BASELINE_HANGING: + return metrics.hanging; + } +} + +text_align_t +resolveTextAlignment(canvas_state_t* state) { + text_align_t alignment = state->textAlignment; + + // Convert start/end to left/right based on direction + if (alignment == TEXT_ALIGNMENT_START) { + return (state->direction == "rtl") ? TEXT_ALIGNMENT_RIGHT : TEXT_ALIGNMENT_LEFT; + } else if (alignment == TEXT_ALIGNMENT_END) { + return (state->direction == "rtl") ? TEXT_ALIGNMENT_LEFT : TEXT_ALIGNMENT_RIGHT; + } + + return alignment; +} + +void +flushSegment( + Segment& segment, + std::vector& shapingWorkList, + std::vector& runs, + uint8_t level, + const FontDescriptor* face, + hb_font_t* hbfont, + hb_buffer_t* buffer, + bool isLastMatch +) { + if (segment.needsReshape && !isLastMatch) { + LOG("==> %zu..%zu needs reshape\n", segment.textStart, segment.textEnd - 1); + shapingWorkList.push_back(ShapeWork{segment.textStart, segment.textEnd}); + } else { + // copy the glyphs and add to the line, yay! + GlyphRun run; + hb_glyph_extents_t extents; + + run.face = face; + run.glyphs.resize(segment.glyphEnd - segment.glyphStart); + run.start = segment.textStart; + run.end = segment.textEnd; + run.level = level; + + hb_glyph_position_t* glyphPositions = hb_buffer_get_glyph_positions(buffer, nullptr); + hb_glyph_info_t* glyphInfos = hb_buffer_get_glyph_infos(buffer, nullptr); + + if (segment.needsReshape) { + LOG("==> %zu..%zu finished with tofu :(\n", segment.textStart, segment.textEnd - 1); + } else { + LOG("==> %zu..%zu success!\n", segment.textStart, segment.textEnd - 1); + } + + if (level & 1) { + int r = segment.glyphEnd - 1; + size_t w = 0; + for (int end = segment.glyphStart - 1; r > end; r--, w++) { + hb_font_get_glyph_extents(hbfont, glyphInfos[r].codepoint, &extents); + run.glyphs[w].id = glyphInfos[r].codepoint; + run.glyphs[w].cluster = glyphInfos[r].cluster; + run.glyphs[w].x_advance = glyphPositions[r].x_advance; + run.glyphs[w].y_advance = glyphPositions[r].y_advance; + run.glyphs[w].x_offset = glyphPositions[r].x_offset; + run.glyphs[w].y_offset = glyphPositions[r].y_offset; + run.glyphs[w].x_bearing = extents.x_bearing; + run.glyphs[w].y_bearing = extents.y_bearing; + run.glyphs[w].width = extents.width; + run.glyphs[w].height = extents.height; + } + } else { + size_t r = segment.glyphStart; + size_t w = 0; + for (; r < segment.glyphEnd; r++, w++) { + hb_font_get_glyph_extents(hbfont, glyphInfos[r].codepoint, &extents); + run.glyphs[w].id = glyphInfos[r].codepoint; + run.glyphs[w].cluster = glyphInfos[r].cluster; + run.glyphs[w].x_advance = glyphPositions[r].x_advance; + run.glyphs[w].y_advance = glyphPositions[r].y_advance; + run.glyphs[w].x_offset = glyphPositions[r].x_offset; + run.glyphs[w].y_offset = glyphPositions[r].y_offset; + run.glyphs[w].x_bearing = extents.x_bearing; + run.glyphs[w].y_bearing = extents.y_bearing; + run.glyphs[w].width = extents.width; + run.glyphs[w].height = extents.height; + } + } + + runs.push_back(run); + } +} + +static void +extractMetrics( + hb_font_t* hbfont, + double fontSize, + uint8_t level, + FontMetrics* metrics +) { + // ascender + hb_position_t ascender; + hb_ot_metrics_get_position_with_fallback( + hbfont, + HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, + &ascender + ); + metrics->ascender = ascender * fontSize / 1000; + + // descender + hb_position_t descender; + hb_ot_metrics_get_position_with_fallback( + hbfont, + HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, + &descender + ); + metrics->descender = descender * fontSize / 1000; + + // Idce + hb_position_t Idce; + bool hasIdce = hb_ot_layout_get_baseline( + hbfont, + HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_CENTRAL, + level & 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, + HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, + &Idce + ); + + // ideo + hb_position_t ideo; + bool hasIdeo = hb_ot_layout_get_baseline( + hbfont, + HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, + level & 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, + HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, + &ideo + ); + + // idtp + hb_position_t idtp; + bool hasIdtp = hb_ot_layout_get_baseline( + hbfont, + HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_TOP_OR_RIGHT, + level & 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, + HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, + &idtp + ); + + if (hasIdce) { + metrics->central = Idce * fontSize / 1000; + } else if (hasIdeo && hasIdtp) { + metrics->central = (ideo + idtp) * fontSize / 2 / 1000; + } else { + metrics->central = (metrics->ascender + metrics->descender) / 2; + } + + if (hasIdeo) { + metrics->ideographic = ideo * fontSize / 1000; + } else { + metrics->ideographic = metrics->descender; + } + + hb_position_t hang; + bool hasHang = hb_ot_layout_get_baseline( + hbfont, + HB_OT_LAYOUT_BASELINE_TAG_HANGING, + level & 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, + HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, + &hang + ); + + if (hasHang) { + metrics->hanging = hang * fontSize / 1000; + } else { + metrics->hanging = 0.6 * fontSize; + } +} + +static void +orderRange( + std::vector& line, + size_t begin, + size_t end +) { + uint8_t minlevel = 0xff; + + LOG("REORDERING %u..%u\n", begin, end); + + for (size_t i = begin; i < end; i++) { + minlevel = std::min(minlevel, line[i].level); + } + + size_t li = begin; // left inner + size_t ri = end - 1; // right inner + + while (li < ri) { + size_t lo = li; // left outer + size_t ro = ri; // right outer + + uint8_t lLevel = line[li].level; + uint8_t rLevel = line[ri].level; + + if (lLevel == minlevel) { + li++; + } else { + while (li <= ri && line[li].level >= lLevel) li++; + if (lo < li) orderRange(line, lo, li); + } + + if (rLevel == minlevel) { + ri--; + } else { + while (li <= ri && line[ri].level >= rLevel) ri--; + if (ri < ro) orderRange(line, ri + 1, ro + 1); + } + + if ((minlevel & 1) && li <= ri) { + size_t lsize = li - lo; + size_t rsize = ro - ri; + size_t swap = std::min(lsize, rsize); + for (size_t i = 0; i < swap; i++) { + std::swap(line[lo + i], line[ri + 1 + i]); + } + // deal with the remainder + if (lsize > rsize) { + std::vector temp(line.begin() + lo + rsize, line.begin() + li); + line.erase(line.begin() + lo + rsize, line.begin() + li); + line.insert(line.begin() + ro + 1 - temp.size(), temp.begin(), temp.end()); + } else if (rsize > lsize) { + std::vector temp(line.begin() + ri + 1 + lsize, line.begin() + ro + 1); + line.erase(line.begin() + ri + 1 + lsize, line.begin() + ro + 1); + line.insert(line.begin() + li, temp.begin(), temp.end()); + } + } + } +} + +static void +orderRunsPhysically( + std::vector& line, + canvas_state_t* state +) { + uint8_t levelOr = 0; + uint8_t levelAnd = 1; + + for (GlyphRun& run : line) { + levelOr |= run.level; + levelAnd &= run.level; + } + + // If none of the levels had the LSB set, all numbers were even + bool allEven = (levelOr & 1) == 0; + // If all of the levels had the LSB set, all numbers were odd + bool allOdd = (levelAnd & 1) == 1; + + if (!allEven && !allOdd) { + orderRange(line, 0, line.size()); + } else if (allOdd) { + std::reverse(line.begin(), line.end()); + } +} + +TextLayout +layoutText( + char16_t* textBuffer, + size_t textLength, + canvas_state_t* state, + InstanceData* data +) { + // All whitespace characters become spaces. Ported from Firefox. Removing + // paragraphs makes segmenting easier, and there's no concept of paragraphs in + // canvas. + for (size_t i = 0; i < textLength; ++i) { + char16_t ch = textBuffer[i]; + if (ch == u'\x09' || ch == u'\x0A' || ch == u'\x0B' || ch == u'\x0C' || + ch == u'\x0D' || ch == u'\x1C' || ch == u'\x1D' || ch == u'\x1E' || + ch == u'\x1F' || ch == u'\x85' || ch == u'\x2029') { + textBuffer[i] = u' '; + } + } + +#if LOG_LEVEL > 0 + auto script_to_string = [](script_t script) -> const char* { + switch (script) { + case SCRIPT_COMMON: return "Common"; + case SCRIPT_LATIN: return "Latin"; + case SCRIPT_GREEK: return "Greek"; + case SCRIPT_CYRILLIC: return "Cyrillic"; + case SCRIPT_ARMENIAN: return "Armenian"; + case SCRIPT_HEBREW: return "Hebrew"; + case SCRIPT_ARABIC: return "Arabic"; + case SCRIPT_INHERITED: return "Inherited"; + case SCRIPT_HIRAGANA: return "Hiragana"; + case SCRIPT_KATAKANA: return "Katakana"; + case SCRIPT_HAN: return "Han"; + case SCRIPT_DEVANAGARI: return "Devanagari"; + case SCRIPT_DESERET: return "Deseret"; + default: return "Unknown"; + } + }; +#endif + + LOG("Processing text with script iterator, buffer size: %zu\n", textLength); + + // Initialize script iterator + ItemizeState itemizer(textBuffer, textLength, state->direction == "ltr" ? 0 : 1); + std::vector fallbacks; + script_t lastScript = SCRIPT_NONE; + double fontSize = state->fontProperties.size; + + hb_buffer_t* buffer = hb_buffer_create(); + hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); + + std::vector shapingWorkList; + TextLayout layout; + + bool foundFont = false; + + while (!itemizer.done && itemizer.offset < textLength) { + size_t start_offset = itemizer.offset; + + itemizeNext(itemizer); + + script_t script = itemizer.script_state.script; + uint8_t level = itemizer.bidi_state.level; + + if (lastScript != script) { + fallbacks.clear(); + data->fontManager.populateFallbackFonts(fallbacks, script); + lastScript = script; + } + + std::vector matches = data->fontManager.query( + state->fontProperties, + data->cppFontSet, + fallbacks + ); + +#if LOG_LEVEL > 0 + std::string cascade = ""; + for (const FontDescriptor* match : matches) { + if (!cascade.empty()) cascade += ", "; + cascade += match->family.get(); + cascade += "/" + std::to_string(match->weight) + "/"; + switch (match->style) { + case FontStyle::Normal: cascade += "Normal"; break; + case FontStyle::Italic: cascade += "Italic"; break; + case FontStyle::Oblique: cascade += "Oblique"; break; + } + } +#endif + + LOG( + "%zu..%zu script=%s dir=%hi cascade=%s\n", + start_offset, + itemizer.offset - 1, + script_to_string(script), + level, + cascade.c_str() + ); + + shapingWorkList.resize(1); + shapingWorkList[0].start = start_offset; + shapingWorkList[0].end = itemizer.offset; + size_t matchIndex = 0; + + while (shapingWorkList.size() && matchIndex < matches.size()) { + auto face = matches[matchIndex]; + face->load(); + if (face->data == nullptr) { + matchIndex++; + continue; + } + + LOG("==> %s\n", face->family.get()); + + // create the HarfBuzz font + hb_blob_t* hbblob = hb_blob_create( + reinterpret_cast(face->data.get()), + face->data_len, + HB_MEMORY_MODE_READONLY, + nullptr, + nullptr + ); + size_t count = hb_face_count(hbblob); + if (count > 1 && face->postscript) { + // Lazily initialize the ttc index for backends that don't provide it (macOS) + for (size_t index = 0; index < count; index++) { + char buf[128]; + unsigned int len = sizeof(buf); + hb_face_t* hbface = hb_face_create(hbblob, index); + hb_ot_name_get_utf8( + hbface, + HB_OT_NAME_ID_POSTSCRIPT_NAME, + hb_language_from_string("en", -1), + &len, + buf + ); + hb_face_destroy(hbface); + + if (strcmp(buf, face->postscript.get()) == 0) { + matches[matchIndex]->index = index; + face->index = index; + break; + } + } + } + hb_face_t* hbface = hb_face_create(hbblob, face->index); + hb_font_t* hbfont = hb_font_create(hbface); + hb_font_set_scale(hbfont, 1000, 1000); + + if (!foundFont) { + extractMetrics(hbfont, fontSize, level, &layout.metrics); + foundFont = true; + } + + // now we have to try to shape everything in shape work, and anything + // we can't shape, we push back onto the shape work for the next font + size_t endWorkForMatch = shapingWorkList.size(); + bool isLastMatch = matchIndex == matches.size() - 1; + for (size_t workIndex = 0; workIndex < endWorkForMatch; workIndex++) { + ShapeWork work = shapingWorkList[workIndex]; + + // shaping time! + hb_buffer_set_length(buffer, 0); + hb_buffer_add_utf16( + buffer, + reinterpret_cast(textBuffer), + textLength, + work.start, + work.end - work.start + ); + hb_buffer_set_script(buffer, script_to_hb_script(script)); + hb_buffer_set_language(buffer, hb_language_from_string(state->lang.c_str(), -1)); + hb_buffer_set_direction( + buffer, + level & 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR + ); + hb_shape(hbfont, buffer, nullptr, 0); + + LOG("==> shaping %zu..%zu\n", work.start, work.end - 1); + + // reversing the entire buffer for RTL text will make the cluster + // numbers increase, so it's easier to iterate in parallel with the + // text itself. the glyphs will be reversed, so we'll have to swap the + // start and end glyph indexes below + if (level & 1) hb_buffer_reverse(buffer); + + // Scan for missing glyphs and create new fragments accordingly + + // glyph iteration + size_t glyphIndex = 0; + unsigned int glyphCount = hb_buffer_get_length(buffer); + hb_glyph_info_t* glyphInfos = hb_buffer_get_glyph_infos(buffer, nullptr); + + bool glyphsNeedReshape = false; + + // text iteration (graphemes, codepoints, and text index) + uint8_t graphemeState = 0; + size_t textIndex = work.start; + uint32_t leftCodepoint = codePointAt(textBuffer, textLength, &textIndex); + size_t graphemeDivider; + + // the working segment + Segment segment {work.start, work.start, glyphIndex, glyphIndex, false}; + + // TODO: assert: textIndex <= w.end + do { + graphemeDivider = textIndex; + while (glyphIndex < glyphCount && glyphInfos[glyphIndex].cluster < graphemeDivider) { + if (glyphInfos[glyphIndex].codepoint == 0) { + glyphsNeedReshape = true; + } + glyphIndex++; + } + + bool isBreak; + uint32_t rightCodepoint; + + if (graphemeDivider >= work.end) { + isBreak = true; + } else { + rightCodepoint = codePointAt(textBuffer, textLength, &textIndex); + isBreak = grapheme_break(&graphemeState, leftCodepoint, rightCodepoint); + } + + if (isBreak) { + if (segment.textStart == segment.textEnd) { + segment.needsReshape = glyphsNeedReshape; // very first grapheme + } + if (glyphsNeedReshape == segment.needsReshape) { + // extend the segment + segment.textEnd = graphemeDivider; + segment.glyphEnd = glyphIndex; + } else { + // finish the last segment, not including this grapheme + flushSegment(segment, shapingWorkList, layout.runs, level, face, hbfont, buffer, isLastMatch); + + // start a new segment that includes this grapheme + segment.textStart = segment.textEnd; + segment.textEnd = graphemeDivider; + segment.glyphStart = segment.glyphEnd; + segment.glyphEnd = glyphIndex; + segment.needsReshape = glyphsNeedReshape; + } + + if (graphemeDivider >= work.end) { + flushSegment(segment, shapingWorkList, layout.runs, level, face, hbfont, buffer, isLastMatch); + } + + glyphsNeedReshape = false; + } + + leftCodepoint = rightCodepoint; + } while (graphemeDivider < work.end); + } + + matchIndex++; + hb_font_destroy(hbfont); + hb_face_destroy(hbface); + hb_blob_destroy(hbblob); + shapingWorkList.erase( + shapingWorkList.begin(), + shapingWorkList.begin() + endWorkForMatch + ); + } + } + + hb_buffer_destroy(buffer); + + std::sort(layout.runs.begin(), layout.runs.end(), compareGlyphRunTextPosition); + orderRunsPhysically(layout.runs, state); + + layout.anchorY = getBaselineAdjustment(state->textBaseline, layout.metrics, fontSize); + + layout.width = 0; + for (GlyphRun& run : layout.runs) { + double toPx = fontSize / 1000; + for (Glyph& glyph : run.glyphs) { + layout.width += glyph.x_advance * toPx; + } + } + + switch (resolveTextAlignment(state)) { + case TEXT_ALIGNMENT_CENTER: + layout.anchorX = layout.width / 2; + break; + case TEXT_ALIGNMENT_RIGHT: + layout.anchorX = layout.width; + break; + default: // TEXT_ALIGNMENT_LEFT + layout.anchorX = 0; + break; + } + + return layout; +} diff --git a/src/FontLayout.h b/src/FontLayout.h new file mode 100644 index 000000000..8c403f4dc --- /dev/null +++ b/src/FontLayout.h @@ -0,0 +1,94 @@ +// Copyright (c) 2024 Caleb Hearon + +#pragma once + +#include +#include +#include + +#include "InstanceData.h" +#include "Font.h" +#include "FontFaceSet.h" +#include "itemize.h" +#include "Canvas.h" +#include "CanvasRenderingContext2d.h" + +struct canvas_state_t; + +struct ShapeWork { + size_t start; + size_t end; +}; + +struct Segment { + size_t textStart; + size_t textEnd; + size_t glyphStart; + size_t glyphEnd; + bool needsReshape; +}; + +struct Glyph { + hb_codepoint_t id; + uint32_t cluster; + hb_position_t x_advance; + hb_position_t y_advance; + hb_position_t x_offset; + hb_position_t y_offset; + hb_position_t x_bearing; + hb_position_t y_bearing; + hb_position_t width; + hb_position_t height; +}; + +struct GlyphRun { + const FontDescriptor* face; + std::vector glyphs; + size_t start; + size_t end; + uint8_t level; +}; + +struct FontMetrics { + double ascender; + double descender; + double central; + double ideographic; + double hanging; +}; + +bool +compareGlyphRunTextPosition(const GlyphRun& a, const GlyphRun& b); + +text_align_t +resolveTextAlignment(canvas_state_t* state); + +void +flushSegment( + Segment& segment, + std::vector& shapingWorkList, + std::vector& line, + uint8_t level, + const FontDescriptor* face, + hb_font_t* hbfont, + hb_buffer_t* buffer, + bool isLastMatch +); + +struct TextLayout { + std::vector runs; + // Increasing values move from the first glyph to the right (always positive) + double anchorX; + // Increasing values move from the baseline up (can be negative) + double anchorY; + FontMetrics metrics{0, 0, 0, 0, 0}; + double width; +}; + +TextLayout +layoutText( + char16_t* textBuffer, + size_t textLength, + canvas_state_t* state, + InstanceData* data +); diff --git a/src/FontManager.cc b/src/FontManager.cc new file mode 100644 index 000000000..d0a31b377 --- /dev/null +++ b/src/FontManager.cc @@ -0,0 +1,269 @@ +#include + +#include "FontManager.h" +#include "FontFaceSet.h" +#include "Util.h" + +bool +compareFamilyNames(const char* str1, size_t len1, const char* str2, size_t len2) { + size_t start1 = 0; + size_t end1 = len1; + size_t start2 = 0; + size_t end2 = len2; + + while (start1 < len1 && std::isspace(str1[start1])) start1++; + while (end1 > start1 && std::isspace(str1[end1 - 1])) end1--; + + while (start2 < len2 && std::isspace(str2[start2])) start2++; + while (end2 > start2 && std::isspace(str2[end2 - 1])) end2--; + + if (end1 - start1 != end2 - start2) return false; + + for (size_t i = 0; i < end1 - start1; i++) { + if (std::tolower(str1[start1 + i]) != std::tolower(str2[start2 + i])) { + return false; + } + } + + return true; +} + + +void +FontManager::narrowByStretch( + std::vector& fonts, + FontProperties& properties +) { + size_t matchBegin = 0; + size_t matchEnd = 0; + uint16_t bestScore = 151; // max distance is 150 + + std::sort( + fonts.begin(), + fonts.end(), + [](FontDescriptor* a, FontDescriptor* b) { + return a->stretch < b->stretch; + } + ); + + for (size_t i = 0; i < fonts.size(); i++) { + if (fonts[i]->stretch == properties.stretch) { + if (matchBegin == matchEnd) { + matchBegin = i; + matchEnd = i + 1; + } else { + matchEnd++; + } + } else { + uint16_t score = properties.stretch > fonts[i]->stretch + ? properties.stretch - fonts[i]->stretch + : fonts[i]->stretch - properties.stretch; + if (score < bestScore) bestScore = score; + } + } + + if (matchBegin == matchEnd) { // no exact match + bool inScore = false; + + if (properties.stretch > 100) { + std::reverse(fonts.begin(), fonts.end()); + } + + for (size_t i = 0; i < fonts.size(); i++) { + uint16_t score = properties.stretch > fonts[i]->stretch + ? properties.stretch - fonts[i]->stretch + : fonts[i]->stretch - properties.stretch; + + if (inScore) { + if (score == bestScore) { + matchEnd++; + } else { + break; + } + } else if (score == bestScore) { + matchBegin = i; + matchEnd = i + 1; + inScore = true; + } + } + } + + if (matchBegin < matchEnd) { + fonts.erase(fonts.begin() + matchEnd, fonts.end()); + fonts.erase(fonts.begin(), fonts.begin() + matchBegin); + } +} + +void +FontManager::narrowByStyle( + std::vector& fonts, + FontProperties& properties +) { + size_t nNormal = 0; + size_t nItalic = 0; + size_t nOblique = 0; + + for (FontDescriptor* font : fonts) { + switch (font->style) { + case FontStyle::Normal: nNormal++; break; + case FontStyle::Italic: nItalic++; break; + case FontStyle::Oblique: nOblique++; break; + } + } + + FontStyle choose; + switch (properties.style) { + case FontStyle::Normal: + choose = nNormal ? FontStyle::Normal : nOblique ? FontStyle::Oblique : FontStyle::Italic; + break; + case FontStyle::Italic: + choose = nItalic ? FontStyle::Italic : nOblique ? FontStyle::Oblique : FontStyle::Normal; + break; + case FontStyle::Oblique: + choose = nOblique ? FontStyle::Oblique : nItalic ? FontStyle::Italic : FontStyle::Normal; + break; + } + + for (size_t i = 0; i < fonts.size(); ) { + if (fonts[i]->style != choose) { + std::swap(fonts[i], fonts[fonts.size() - 1]); + fonts.pop_back(); + } else { + i++; + } + } +} + +FontDescriptor* +FontManager::narrowByWeight( + std::vector fonts, + FontProperties& properties +) { + std::sort( + fonts.begin(), + fonts.end(), + [](FontDescriptor* a, FontDescriptor* b) { + return a->weight < b->weight; + } + ); + + assert(fonts.size() && "Precondition failed: 1 or 0 fonts in the set"); + + for (FontDescriptor* font : fonts) { + if (font->weight == properties.weight) { + return font; + } + } + + FontDescriptor* bestBelow = nullptr; + size_t bestBelowDistance = 900; // max possible is 800 + FontDescriptor* bestAbove = nullptr; + size_t bestAboveDistance = 900; + size_t divider = properties.weight == 400 ? 500 + : properties.weight == 500 ? 400 + : properties.weight; + + for (FontDescriptor* font : fonts) { + size_t distance = font->weight < properties.weight + ? properties.weight - font->weight + : font->weight - properties.weight; + + if (font->weight < divider) { + if (distance < bestBelowDistance) { + bestBelow = font; + bestBelowDistance = distance; + } + } else { + if (distance < bestAboveDistance) { + bestAbove = font; + bestAboveDistance = distance; + } + } + } + + if (bestBelow && bestAbove) { + if (bestBelowDistance == bestAboveDistance) { + return divider <= 500 ? bestBelow : bestAbove; + } else if (bestBelowDistance < bestAboveDistance) { + return bestBelow; + } else { + return bestAbove; + } + } else { + return bestBelow ? bestBelow : bestAbove; + } +} + +/** + * NOTE: the FontDescriptor is owned by the FontManager; do not use it again! + */ +std::vector +FontManager::query( + FontProperties& properties, + FontFaceSet* registered, + std::vector& fallbacks +) { + std::vector allFamilyResults; + std::vector familyResults; + + auto maybeAdd = [&](const std::string& family, FontDescriptor* desc) { + if ( + compareFamilyNames( + family.c_str(), + family.size(), + desc->family.get(), + strlen(desc->family.get()) + ) && std::find( + familyResults.begin(), + familyResults.end(), + desc + ) == familyResults.end() + ) familyResults.push_back(desc); + }; + + for (const std::string& family : properties.families) { + auto genericFamilies = getGenericList(family); + if (genericFamilies) { + for (const std::string& family : **genericFamilies) { + for (FontDescriptor& desc : system_fonts) { + maybeAdd(family, &desc); + } + } + } else { + for (auto& entry : registered->facesData) { + if (entry.face != nullptr) maybeAdd(family, &(entry.face->descriptor)); + } + for (FontDescriptor& desc : system_fonts) { + maybeAdd(family, &desc); + } + } + + if (familyResults.size() == 1) { + allFamilyResults.push_back(familyResults[0]); + familyResults.clear(); + } else if (familyResults.size() > 1) { + narrowByStretch(familyResults, properties); + narrowByStyle(familyResults, properties); + allFamilyResults.push_back(narrowByWeight(familyResults, properties)); + familyResults.clear(); + } + } + + for (const std::string& fallback : fallbacks) { + for (FontDescriptor& desc : system_fonts) { + maybeAdd(fallback, &desc); + } + + if (familyResults.size() == 1) { + allFamilyResults.push_back(familyResults[0]); + familyResults.clear(); + } else if (familyResults.size() > 1) { + narrowByStretch(familyResults, properties); + narrowByStyle(familyResults, properties); + allFamilyResults.push_back(narrowByWeight(familyResults, properties)); + familyResults.clear(); + } + } + + return allFamilyResults; +} diff --git a/src/FontManager.h b/src/FontManager.h new file mode 100644 index 000000000..ec473ccb8 --- /dev/null +++ b/src/FontManager.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "Font.h" +#include "FontFaceSet.h" +#include "unicode.h" + +class FontManager { + public: + virtual ~FontManager() = default; + + virtual void readSystemFonts() = 0; + + virtual void populateFallbackFonts( + std::vector& families, + script_t script + ) = 0; + + virtual std::optional*> getGenericList( + const std::string& generic + ) = 0; + + std::vector query( + FontProperties& properties, + FontFaceSet* registered, + std::vector& fallbacks + ); + + private: + void narrowByStretch( + std::vector& fonts, + FontProperties& properties + ); + + void narrowByStyle( + std::vector& fonts, + FontProperties& properties + ); + + FontDescriptor* narrowByWeight( + std::vector fonts, + FontProperties& properties + ); + + protected: + std::vector system_fonts; +}; diff --git a/src/FontManagerLinux.cc b/src/FontManagerLinux.cc new file mode 100644 index 000000000..004bbcf4a --- /dev/null +++ b/src/FontManagerLinux.cc @@ -0,0 +1,774 @@ +// Copyright (c) 2025 Caleb Hearon + +#include + +#include +#include + +#include "FontManagerLinux.h" +#include "Font.h" +#include "unicode.h" + +// Fontconfig types and constants (from fontconfig/fontconfig.h) +typedef unsigned char FcChar8; +typedef unsigned int FcChar32; +typedef int FcBool; + +typedef struct _FcPattern FcPattern; +typedef struct _FcCharSet FcCharSet; +typedef struct _FcConfig FcConfig; +typedef struct _FcObjectSet FcObjectSet; + +// FcFontSet layout is part of fontconfig's stable public ABI +typedef struct _FcFontSet { + int nfont; + int sfont; + FcPattern **fonts; +} FcFontSet; + +typedef enum _FcResult { + FcResultMatch, FcResultNoMatch, FcResultTypeMismatch, + FcResultNoId, FcResultOutOfMemory, +} FcResult; + +typedef enum _FcMatchKind { + FcMatchPattern, FcMatchFont, FcMatchScan, +} FcMatchKind; + +static constexpr FcBool FcFalse = 0; +static constexpr FcBool FcTrue = 1; + +static constexpr const char* FC_FAMILY = "family"; +static constexpr const char* FC_FILE = "file"; +static constexpr const char* FC_INDEX = "index"; +static constexpr const char* FC_WEIGHT = "weight"; +static constexpr const char* FC_WIDTH = "width"; +static constexpr const char* FC_SLANT = "slant"; +static constexpr const char* FC_LANG = "lang"; +static constexpr const char* FC_CHARSET = "charset"; +static constexpr const char* FC_COLOR = "color"; + +static constexpr int FC_SLANT_ROMAN = 0; +static constexpr int FC_SLANT_ITALIC = 100; +static constexpr int FC_SLANT_OBLIQUE = 110; + +static constexpr int FC_WEIGHT_NORMAL = 80; // FC_WEIGHT_REGULAR in the header +static constexpr int FC_WIDTH_NORMAL = 100; + +struct Fc { + void* handle = nullptr; + + using fn_Init = FcBool (*)(); + using fn_PatternCreate = FcPattern* (*)(); + using fn_PatternDestroy = void (*)(FcPattern*); + using fn_PatternAddString = FcBool (*)(FcPattern*, const char*, const FcChar8*); + using fn_PatternAddBool = FcBool (*)(FcPattern*, const char*, FcBool); + using fn_PatternAddCharSet = FcBool (*)(FcPattern*, const char*, const FcCharSet*); + using fn_PatternGetString = FcResult (*)(const FcPattern*, const char*, int, FcChar8**); + using fn_PatternGetInteger = FcResult (*)(const FcPattern*, const char*, int, int*); + using fn_ConfigSubstitute = FcBool (*)(FcConfig*, FcPattern*, FcMatchKind); + using fn_DefaultSubstitute = void (*)(FcPattern*); + using fn_FontMatch = FcPattern* (*)(FcConfig*, FcPattern*, FcResult*); + using fn_FontSort = FcFontSet* (*)(FcConfig*, FcPattern*, FcBool, FcCharSet**, FcResult*); + using fn_FontList = FcFontSet* (*)(FcConfig*, FcPattern*, FcObjectSet*); + using fn_FontSetDestroy = void (*)(FcFontSet*); + using fn_ObjectSetCreate = FcObjectSet* (*)(); + using fn_ObjectSetAdd = FcBool (*)(FcObjectSet*, const char*); + using fn_ObjectSetDestroy = void (*)(FcObjectSet*); + using fn_CharSetCreate = FcCharSet* (*)(); + using fn_CharSetDestroy = void (*)(FcCharSet*); + using fn_CharSetAddChar = FcBool (*)(FcCharSet*, FcChar32); + using fn_WeightToOpenType = int (*)(int); + + fn_Init Init = nullptr; + fn_PatternCreate PatternCreate = nullptr; + fn_PatternDestroy PatternDestroy = nullptr; + fn_PatternAddString PatternAddString = nullptr; + fn_PatternAddBool PatternAddBool = nullptr; + fn_PatternAddCharSet PatternAddCharSet = nullptr; + fn_PatternGetString PatternGetString = nullptr; + fn_PatternGetInteger PatternGetInteger = nullptr; + fn_ConfigSubstitute ConfigSubstitute = nullptr; + fn_DefaultSubstitute DefaultSubstitute = nullptr; + fn_FontMatch FontMatch = nullptr; + fn_FontSort FontSort = nullptr; + fn_FontList FontList = nullptr; + fn_FontSetDestroy FontSetDestroy = nullptr; + fn_ObjectSetCreate ObjectSetCreate = nullptr; + fn_ObjectSetAdd ObjectSetAdd = nullptr; + fn_ObjectSetDestroy ObjectSetDestroy = nullptr; + fn_CharSetCreate CharSetCreate = nullptr; + fn_CharSetDestroy CharSetDestroy = nullptr; + fn_CharSetAddChar CharSetAddChar = nullptr; + fn_WeightToOpenType WeightToOpenType = nullptr; +}; + +static Fc* +loadFc() { + static Fc fc; + + if (fc.handle || fc.PatternCreate) + return fc.handle ? &fc : nullptr; + + fc.handle = dlopen("libfontconfig.so.1", RTLD_NOW); + if (!fc.handle) return nullptr; + + fc.Init = (Fc::fn_Init) dlsym(fc.handle, "FcInit"); + fc.PatternCreate = (Fc::fn_PatternCreate) dlsym(fc.handle, "FcPatternCreate"); + fc.PatternDestroy = (Fc::fn_PatternDestroy) dlsym(fc.handle, "FcPatternDestroy"); + fc.PatternAddString = (Fc::fn_PatternAddString) dlsym(fc.handle, "FcPatternAddString"); + fc.PatternAddBool = (Fc::fn_PatternAddBool) dlsym(fc.handle, "FcPatternAddBool"); + fc.PatternAddCharSet = (Fc::fn_PatternAddCharSet)dlsym(fc.handle, "FcPatternAddCharSet"); + fc.PatternGetString = (Fc::fn_PatternGetString) dlsym(fc.handle, "FcPatternGetString"); + fc.PatternGetInteger = (Fc::fn_PatternGetInteger)dlsym(fc.handle, "FcPatternGetInteger"); + fc.ConfigSubstitute = (Fc::fn_ConfigSubstitute) dlsym(fc.handle, "FcConfigSubstitute"); + fc.DefaultSubstitute = (Fc::fn_DefaultSubstitute)dlsym(fc.handle, "FcDefaultSubstitute"); + fc.FontMatch = (Fc::fn_FontMatch) dlsym(fc.handle, "FcFontMatch"); + fc.FontSort = (Fc::fn_FontSort) dlsym(fc.handle, "FcFontSort"); + fc.FontList = (Fc::fn_FontList) dlsym(fc.handle, "FcFontList"); + fc.FontSetDestroy = (Fc::fn_FontSetDestroy) dlsym(fc.handle, "FcFontSetDestroy"); + fc.ObjectSetCreate = (Fc::fn_ObjectSetCreate) dlsym(fc.handle, "FcObjectSetCreate"); + fc.ObjectSetAdd = (Fc::fn_ObjectSetAdd) dlsym(fc.handle, "FcObjectSetAdd"); + fc.ObjectSetDestroy = (Fc::fn_ObjectSetDestroy) dlsym(fc.handle, "FcObjectSetDestroy"); + fc.CharSetCreate = (Fc::fn_CharSetCreate) dlsym(fc.handle, "FcCharSetCreate"); + fc.CharSetDestroy = (Fc::fn_CharSetDestroy) dlsym(fc.handle, "FcCharSetDestroy"); + fc.CharSetAddChar = (Fc::fn_CharSetAddChar) dlsym(fc.handle, "FcCharSetAddChar"); + fc.WeightToOpenType = (Fc::fn_WeightToOpenType) dlsym(fc.handle, "FcWeightToOpenType"); + + if (!fc.PatternCreate || !fc.FontMatch || !fc.PatternGetString) { + dlclose(fc.handle); + fc.handle = nullptr; + return nullptr; + } + + return &fc; +} + +static std::string +familyForGeneric(Fc* fc, const char* generic) { + FcPattern* pattern = fc->PatternCreate(); + fc->PatternAddString(pattern, FC_FAMILY, reinterpret_cast(generic)); + fc->ConfigSubstitute(nullptr, pattern, FcMatchPattern); + fc->DefaultSubstitute(pattern); + + FcResult result; + FcPattern* match = fc->FontMatch(nullptr, pattern, &result); + + std::string name; + if (match) { + FcChar8* family = nullptr; + if (fc->PatternGetString(match, FC_FAMILY, 0, &family) == FcResultMatch) { + name = reinterpret_cast(family); + } + fc->PatternDestroy(match); + } + + fc->PatternDestroy(pattern); + return name; +} + +static void +addFamilyFromFcSort(Fc* fc, FcPattern* pattern, std::vector& results) { + fc->ConfigSubstitute(nullptr, pattern, FcMatchPattern); + fc->DefaultSubstitute(pattern); + + FcResult result; + FcFontSet* set = fc->FontSort(nullptr, pattern, FcFalse, nullptr, &result); + + for (int i = 0; i < set->nfont; i++) { + FcChar8* family = nullptr; + if (fc->PatternGetString(set->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch) { + results.push_back(std::string((char*)family)); + break; + } + } + + fc->FontSetDestroy(set); +} + +static void +addFamilyForChars( + Fc* fc, + std::vector& list, + FcPattern* pattern, + uint32_t cp1, + uint32_t cp2 +) { + FcCharSet* cset = fc->CharSetCreate(); + fc->CharSetAddChar(cset, cp1); + fc->CharSetAddChar(cset, cp2); + fc->PatternAddString(pattern, FC_FAMILY, (FcChar8*)"sans"); + fc->PatternAddCharSet(pattern, FC_CHARSET, cset); + addFamilyFromFcSort(fc, pattern, list); + fc->CharSetDestroy(cset); +} + +const std::vector& +FontManagerLinux::langFamilies(const std::string& lang) { + auto it = lang_cache.find(lang); + if (it != lang_cache.end()) return it->second; + + static const std::vector empty; + Fc* fc = loadFc(); + if (!fc) return lang_cache.emplace(lang, empty).first->second; + + FcPattern* pattern = fc->PatternCreate(); + fc->PatternAddString(pattern, FC_LANG, reinterpret_cast(lang.c_str())); + std::vector results; + addFamilyFromFcSort(fc, pattern, results); + fc->PatternDestroy(pattern); + + return lang_cache.emplace(lang, std::move(results)).first->second; +} + +static uint16_t +convertWeight(Fc* fc, int fcWeight) { + int ot = fc->WeightToOpenType(fcWeight); + if (ot < 1) return 400; + if (ot > 1000) return 1000; + return static_cast(ot); +} + +static FontStyle +convertSlant(int slant) { + switch (slant) { + case FC_SLANT_ITALIC: return FontStyle::Italic; + case FC_SLANT_OBLIQUE: return FontStyle::Oblique; + case FC_SLANT_ROMAN: + default: return FontStyle::Normal; + } +} + +void +FontManagerLinux::readSystemFonts() { + Fc* fc = loadFc(); + if (!fc) return; + + fc->Init(); + + for (const char* generic : {"serif", "sans-serif", "monospace", "cursive", "fantasy"}) { + std::string resolved = familyForGeneric(fc, generic); + if (!resolved.empty()) generic_fonts[generic].push_back(std::move(resolved)); + } + + { + FcPattern* pattern = fc->PatternCreate(); + fc->PatternAddBool(pattern, FC_COLOR, FcTrue); + addFamilyForChars(fc, emoji_fonts, pattern, 0x1f602, 0x1f602); // face with tears of joy + fc->PatternDestroy(pattern); + } + + { + FcPattern* pattern = fc->PatternCreate(); + addFamilyForChars(fc, symbol_fonts, pattern, 0x2192, 0x1f846); // right arow, thick right arrow + fc->PatternDestroy(pattern); + } + + FcPattern* pattern = fc->PatternCreate(); + FcObjectSet* objects = fc->ObjectSetCreate(); + fc->ObjectSetAdd(objects, FC_FAMILY); + fc->ObjectSetAdd(objects, FC_FILE); + fc->ObjectSetAdd(objects, FC_INDEX); + fc->ObjectSetAdd(objects, FC_WEIGHT); + fc->ObjectSetAdd(objects, FC_WIDTH); + fc->ObjectSetAdd(objects, FC_SLANT); + + FcFontSet* set = fc->FontList(nullptr, pattern, objects); + + if (set) { + system_fonts.reserve(set->nfont); + + for (int i = 0; i < set->nfont; i++) { + FcPattern* font = set->fonts[i]; + + FcChar8* file = nullptr; + FcChar8* family = nullptr; + if (fc->PatternGetString(font, FC_FILE, 0, &file) != FcResultMatch) continue; + if (fc->PatternGetString(font, FC_FAMILY, 0, &family) != FcResultMatch) continue; + + FontDescriptor desc; + + size_t fileLength = std::strlen((const char*)file) + 1; + desc.url = std::make_unique(fileLength); + std::memcpy(desc.url.get(), file, fileLength); + + size_t familyLength = std::strlen((const char*)family) + 1; + desc.family = std::make_unique(familyLength); + std::memcpy(desc.family.get(), family, familyLength); + + int index = 0; + fc->PatternGetInteger(font, FC_INDEX, 0, &index); + desc.index = (size_t)index; + + int weight = FC_WEIGHT_NORMAL; + fc->PatternGetInteger(font, FC_WEIGHT, 0, &weight); + desc.weight = convertWeight(fc, weight); + + int width = FC_WIDTH_NORMAL; + fc->PatternGetInteger(font, FC_WIDTH, 0, &width); + desc.stretch = (uint16_t)(width); + + int slant = FC_SLANT_ROMAN; + fc->PatternGetInteger(font, FC_SLANT, 0, &slant); + desc.style = convertSlant(slant); + + system_fonts.push_back(std::move(desc)); + } + + fc->FontSetDestroy(set); + } + + fc->ObjectSetDestroy(objects); + fc->PatternDestroy(pattern); +} + +void +FontManagerLinux::populateFallbackFonts( + std::vector& families, + script_t script +) { + const char* lang = nullptr; + + switch (script) { + case SCRIPT_COMMON: + case SCRIPT_INHERITED: + case SCRIPT_LATIN: + lang = "en"; + break; + case SCRIPT_CYRILLIC: lang = "ru"; break; + case SCRIPT_GREEK: lang = "el"; break; + case SCRIPT_ARMENIAN: lang = "hy"; break; + case SCRIPT_HEBREW: lang = "he"; break; + case SCRIPT_ARABIC: lang = "ar"; break; + case SCRIPT_SYRIAC: lang = "syr"; break; + case SCRIPT_THAANA: lang = "dv"; break; + case SCRIPT_DEVANAGARI: lang = "hi"; break; + case SCRIPT_BENGALI: lang = "bn"; break; + case SCRIPT_GUJARATI: lang = "gu"; break; + case SCRIPT_GURMUKHI: lang = "pa"; break; + case SCRIPT_KANNADA: lang = "kn"; break; + case SCRIPT_MALAYALAM: lang = "ml"; break; + case SCRIPT_ORIYA: lang = "or"; break; + case SCRIPT_TAMIL: lang = "ta"; break; + case SCRIPT_TELUGU: lang = "te"; break; + case SCRIPT_SINHALA: lang = "si"; break; + case SCRIPT_THAI: lang = "th"; break; + case SCRIPT_LAO: lang = "lo"; break; + case SCRIPT_TIBETAN: lang = "bo"; break; + case SCRIPT_MYANMAR: lang = "my"; break; + case SCRIPT_KHMER: lang = "km"; break; + case SCRIPT_GEORGIAN: lang = "ka"; break; + case SCRIPT_ETHIOPIC: lang = "am"; break; + case SCRIPT_CHEROKEE: lang = "chr"; break; + case SCRIPT_CANADIAN_ABORIGINAL: lang = "iu"; break; + case SCRIPT_YI: lang = "ii"; break; + case SCRIPT_COPTIC: lang = "cop"; break; + case SCRIPT_GOTHIC: lang = "got"; break; + case SCRIPT_NKO: lang = "nqo"; break; + case SCRIPT_TIFINAGH: lang = "ber-ma"; break; + case SCRIPT_MONGOLIAN: lang = "mn-cn"; break; + case SCRIPT_HANGUL: lang = "ko"; break; + case SCRIPT_HAN: + case SCRIPT_BOPOMOFO: + lang = "zh-cn"; + break; + case SCRIPT_HIRAGANA: + case SCRIPT_KATAKANA: + lang = "ja"; + break; + + // These scripts don't appear to be covered by FontConfig .orth files + case SCRIPT_DESERET: + families.push_back("Noto Sans Deseret"); + break; + case SCRIPT_OGHAM: + families.push_back("Noto Sans Ogham"); + break; + case SCRIPT_OLD_ITALIC: + families.push_back("Noto Sans Old Italic"); + break; + case SCRIPT_RUNIC: + families.push_back("Noto Sans Runic"); + break; + case SCRIPT_TAGALOG: + families.push_back("Noto Sans Tagalog"); + break; + case SCRIPT_HANUNOO: + families.push_back("Noto Sans Hanunoo"); + break; + case SCRIPT_BUHID: + families.push_back("Noto Sans Buhid"); + break; + case SCRIPT_TAGBANWA: + families.push_back("Noto Sans Tagbanwa"); + break; + case SCRIPT_BRAILLE: + families.push_back("Noto Sans Symbols2"); + break; + case SCRIPT_CYPRIOT: + families.push_back("Noto Sans Cypriot"); + break; + case SCRIPT_LIMBU: + families.push_back("Noto Sans Limbu"); + break; + case SCRIPT_LINEAR_B: + families.push_back("Noto Sans Linear B"); + break; + case SCRIPT_OSMANYA: + families.push_back("Noto Sans Osmanya"); + break; + case SCRIPT_SHAVIAN: + families.push_back("Noto Sans Shavian"); + break; + case SCRIPT_TAI_LE: + families.push_back("Noto Sans Tai Le"); + break; + case SCRIPT_UGARITIC: + families.push_back("Noto Sans Ugaritic"); + break; + case SCRIPT_BUGINESE: + families.push_back("Noto Sans Buginese"); + break; + case SCRIPT_GLAGOLITIC: + families.push_back("Noto Sans Glagolitic"); + break; + case SCRIPT_KHAROSHTHI: + families.push_back("Noto Sans Kharoshthi"); + break; + case SCRIPT_SYLOTI_NAGRI: + families.push_back("Noto Sans Syloti Nagri"); + break; + case SCRIPT_NEW_TAI_LUE: + families.push_back("Noto Sans New Tai Lue"); + break; + case SCRIPT_OLD_PERSIAN: + families.push_back("Noto Sans Old Persian"); + break; + case SCRIPT_BALINESE: + families.push_back("Noto Sans Balinese"); + break; + case SCRIPT_BATAK: + families.push_back("Noto Sans Batak"); + break; + case SCRIPT_BRAHMI: + families.push_back("Noto Sans Brahmi"); + break; + case SCRIPT_CHAM: + families.push_back("Noto Sans Cham"); + break; + case SCRIPT_EGYPTIAN_HIEROGLYPHS: + families.push_back("Noto Sans Egyptian Hieroglyphs"); + break; + case SCRIPT_PAHAWH_HMONG: + families.push_back("Noto Sans Pahawh Hmong"); + break; + case SCRIPT_OLD_HUNGARIAN: + families.push_back("Noto Sans Old Hungarian"); + break; + case SCRIPT_JAVANESE: + families.push_back("Noto Sans Javanese"); + break; + case SCRIPT_KAYAH_LI: + families.push_back("Noto Sans Kayah Li"); + break; + case SCRIPT_LEPCHA: + families.push_back("Noto Sans Lepcha"); + break; + case SCRIPT_LINEAR_A: + families.push_back("Noto Sans Linear A"); + break; + case SCRIPT_MANDAIC: + families.push_back("Noto Sans Mandaic"); + break; + case SCRIPT_OLD_TURKIC: + families.push_back("Noto Sans Old Turkic"); + break; + case SCRIPT_OLD_PERMIC: + families.push_back("Noto Sans Old Permic"); + break; + case SCRIPT_PHAGS_PA: + families.push_back("Noto Sans PhagsPa"); + break; + case SCRIPT_PHOENICIAN: + families.push_back("Noto Sans Phoenician"); + break; + case SCRIPT_MIAO: + families.push_back("Noto Sans Miao"); + break; + case SCRIPT_VAI: + families.push_back("Noto Sans Vai"); + break; + case SCRIPT_CUNEIFORM: + families.push_back("Noto Sans Cuneiform"); + break; + case SCRIPT_CARIAN: + families.push_back("Noto Sans Carian"); + break; + case SCRIPT_TAI_THAM: + families.push_back("Noto Sans Tai Tham"); + break; + case SCRIPT_LYCIAN: + families.push_back("Noto Sans Lycian"); + break; + case SCRIPT_LYDIAN: + families.push_back("Noto Sans Lydian"); + break; + case SCRIPT_OL_CHIKI: + families.push_back("Noto Sans Ol Chiki"); + break; + case SCRIPT_REJANG: + families.push_back("Noto Sans Rejang"); + break; + case SCRIPT_SAURASHTRA: + families.push_back("Noto Sans Saurashtra"); + break; + case SCRIPT_SUNDANESE: + families.push_back("Noto Sans Sundanese"); + break; + case SCRIPT_MEETEI_MAYEK: + families.push_back("Noto Sans Meetei Mayek"); + break; + case SCRIPT_IMPERIAL_ARAMAIC: + families.push_back("Noto Sans Imperial Aramaic"); + break; + case SCRIPT_AVESTAN: + families.push_back("Noto Sans Avestan"); + break; + case SCRIPT_CHAKMA: + families.push_back("Noto Sans Chakma"); + break; + case SCRIPT_KAITHI: + families.push_back("Noto Sans Kaithi"); + break; + case SCRIPT_MANICHAEAN: + families.push_back("Noto Sans Manichaean"); + break; + case SCRIPT_INSCRIPTIONAL_PAHLAVI: + families.push_back("Noto Sans Inscriptional Pahlavi"); + break; + case SCRIPT_PSALTER_PAHLAVI: + families.push_back("Noto Sans Psalter Pahlavi"); + break; + case SCRIPT_INSCRIPTIONAL_PARTHIAN: + families.push_back("Noto Sans Inscriptional Parthian"); + break; + case SCRIPT_SAMARITAN: + families.push_back("Noto Sans Samaritan"); + break; + case SCRIPT_TAI_VIET: + families.push_back("Noto Sans Tai Viet"); + break; + case SCRIPT_BAMUM: + families.push_back("Noto Sans Bamum"); + break; + case SCRIPT_LISU: + families.push_back("Noto Sans Lisu"); + break; + case SCRIPT_OLD_SOUTH_ARABIAN: + families.push_back("Noto Sans Old South Arabian"); + break; + case SCRIPT_BASSA_VAH: + families.push_back("Noto Sans Bassa Vah"); + break; + case SCRIPT_DUPLOYAN: + families.push_back("Noto Sans Duployan"); + break; + case SCRIPT_ELBASAN: + families.push_back("Noto Sans Elbasan"); + break; + case SCRIPT_GRANTHA: + families.push_back("Noto Sans Grantha"); + break; + case SCRIPT_MENDE_KIKAKUI: + families.push_back("Noto Sans Mende Kikakui"); + break; + case SCRIPT_MEROITIC_CURSIVE: + case SCRIPT_MEROITIC_HIEROGLYPHS: + families.push_back("Noto Sans Meroitic"); + break; + case SCRIPT_OLD_NORTH_ARABIAN: + families.push_back("Noto Sans Old North Arabian"); + break; + case SCRIPT_NABATAEAN: + families.push_back("Noto Sans Nabataean"); + break; + case SCRIPT_PALMYRENE: + families.push_back("Noto Sans Palmyrene"); + break; + case SCRIPT_KHUDAWADI: + families.push_back("Noto Sans Khudawadi"); + break; + case SCRIPT_WARANG_CITI: + families.push_back("Noto Sans Warang Citi"); + break; + case SCRIPT_MRO: + families.push_back("Noto Sans Mro"); + break; + case SCRIPT_SHARADA: + families.push_back("Noto Sans Sharada"); + break; + case SCRIPT_SORA_SOMPENG: + families.push_back("Noto Sans Sora Sompeng"); + break; + case SCRIPT_TAKRI: + families.push_back("Noto Sans Takri"); + break; + case SCRIPT_KHOJKI: + families.push_back("Noto Sans Khojki"); + break; + case SCRIPT_TIRHUTA: + families.push_back("Noto Sans Tirhuta"); + break; + case SCRIPT_CAUCASIAN_ALBANIAN: + families.push_back("Noto Sans Caucasian Albanian"); + break; + case SCRIPT_MAHAJANI: + families.push_back("Noto Sans Mahajani"); + break; + case SCRIPT_AHOM: + families.push_back("Noto Serif Ahom"); + break; + case SCRIPT_HATRAN: + families.push_back("Noto Sans Hatran"); + break; + case SCRIPT_MODI: + families.push_back("Noto Sans Modi"); + break; + case SCRIPT_MULTANI: + families.push_back("Noto Sans Multani"); + break; + case SCRIPT_PAU_CIN_HAU: + families.push_back("Noto Sans Pau Cin Hau"); + break; + case SCRIPT_SIDDHAM: + families.push_back("Noto Sans Siddham"); + break; + case SCRIPT_ADLAM: + families.push_back("Noto Sans Adlam"); + break; + case SCRIPT_BHAIKSUKI: + families.push_back("Noto Sans Bhaiksuki"); + break; + case SCRIPT_MARCHEN: + families.push_back("Noto Sans Marchen"); + break; + case SCRIPT_NEWA: + families.push_back("Noto Sans Newa"); + break; + case SCRIPT_OSAGE: + families.push_back("Noto Sans Osage"); + break; + case SCRIPT_HANIFI_ROHINGYA: + families.push_back("Noto Sans Hanifi Rohingya"); + break; + case SCRIPT_WANCHO: + families.push_back("Noto Sans Wancho"); + break; + case SCRIPT_ANATOLIAN_HIEROGLYPHS: + families.push_back("Noto Sans Anatolian Hieroglyphs"); + break; + case SCRIPT_NUSHU: + families.push_back("Noto Sans Nushu"); + break; + case SCRIPT_TANGUT: + families.push_back("Noto Serif Tangut"); + break; + case SCRIPT_MASARAM_GONDI: + families.push_back("Noto Sans Masaram Gondi"); + break; + case SCRIPT_GUNJALA_GONDI: + families.push_back("Noto Sans Gunjala Gondi"); + break; + case SCRIPT_SOYOMBO: + families.push_back("Noto Sans Soyombo"); + break; + case SCRIPT_ZANABAZAR_SQUARE: + families.push_back("Noto Sans Zanabazar Square"); + break; + case SCRIPT_DOGRA: + families.push_back("Noto Sans Dogra"); + break; + case SCRIPT_MAKASAR: + families.push_back("Noto Sans Makasar"); + break; + case SCRIPT_MEDEFAIDRIN: + families.push_back("Noto Sans Medefaidrin"); + break; + case SCRIPT_SOGDIAN: + families.push_back("Noto Sans Sogdian"); + break; + case SCRIPT_OLD_SOGDIAN: + families.push_back("Noto Sans Old Sogdian"); + break; + case SCRIPT_ELYMAIC: + families.push_back("Noto Sans Elymaic"); + break; + case SCRIPT_NYIAKENG_PUACHUE_HMONG: + families.push_back("Noto Sans Nyiakeng Puachue Hmong"); + break; + case SCRIPT_NANDINAGARI: + families.push_back("Noto Sans Nandinagari"); + break; + case SCRIPT_CHORASMIAN: + families.push_back("Noto Sans Chorasmian"); + break; + case SCRIPT_DIVES_AKURU: + families.push_back("Noto Sans Dives Akuru"); + break; + case SCRIPT_KHITAN_SMALL_SCRIPT: + families.push_back("Noto Sans Khitan Small Script"); + break; + case SCRIPT_YEZIDI: + families.push_back("Noto Sans Yezidi"); + break; + case SCRIPT_OLD_UYGHUR: + families.push_back("Noto Sans Old Uyghur"); + break; + case SCRIPT_TANGSA: + families.push_back("Noto Sans Tangsa"); + break; + case SCRIPT_TOTO: + families.push_back("Noto Sans Toto"); + break; + case SCRIPT_VITHKUQI: + families.push_back("Noto Sans Vithkuqi"); + break; + case SCRIPT_KAWI: + families.push_back("Noto Sans Kawi"); + break; + case SCRIPT_NAG_MUNDARI: + families.push_back("Noto Sans Nag Mundari"); + break; + case SCRIPT_SIGNWRITING: + families.push_back("Noto Sans SignWriting"); + break; + case SCRIPT_CYPRO_MINOAN: + families.push_back("Noto Sans Cypro Minoan"); + break; + + case SCRIPT_NONE: + case SCRIPT_GARAY: + case SCRIPT_GURUNG_KHEMA: + case SCRIPT_KIRAT_RAI: + case SCRIPT_OL_ONAL: + case SCRIPT_SUNUWAR: + case SCRIPT_TODHRI: + case SCRIPT_TULU_TIGALARI: + break; + } + + if (lang) { + const std::vector& fams = langFamilies(lang); + families.insert(families.end(), fams.begin(), fams.end()); + } + + families.insert(families.end(), emoji_fonts.begin(), emoji_fonts.end()); + families.insert(families.end(), symbol_fonts.begin(), symbol_fonts.end()); +} + +std::optional*> +FontManagerLinux::getGenericList(const std::string& generic) { + auto it = generic_fonts.find(generic); + if (it != generic_fonts.end() && !it->second.empty()) return &it->second; + return std::nullopt; +} diff --git a/src/FontManagerLinux.h b/src/FontManagerLinux.h new file mode 100644 index 000000000..8ea24e0a4 --- /dev/null +++ b/src/FontManagerLinux.h @@ -0,0 +1,22 @@ +#pragma once + +#include "FontManager.h" +#include +#include +#include +#include + +class FontManagerLinux : public FontManager { + public: + void readSystemFonts() override; + void populateFallbackFonts(std::vector& families, script_t script) override; + std::optional*> getGenericList(const std::string& generic) override; + + private: + std::vector emoji_fonts; + std::vector symbol_fonts; + + std::unordered_map> generic_fonts; + std::unordered_map> lang_cache; + const std::vector& langFamilies(const std::string& lang); +}; diff --git a/src/FontManagerMacos.cc b/src/FontManagerMacos.cc new file mode 100644 index 000000000..7c2fbc9e2 --- /dev/null +++ b/src/FontManagerMacos.cc @@ -0,0 +1,673 @@ +// Copyright (c) 2025 Caleb Hearon +// +// References: +// - https://github.com/foliojs/font-manager +// - https://searchfox.org/firefox-main/rev/30ea9a2fd7271e9c731df414bd80e46edc3190eb/gfx/thebes/CoreTextFontList.cpp + +#include +#include +#include +#include +#include +#include + +#include "FontManagerMacos.h" +#include "Font.h" +#include "unicode.h" + +// Forward declarations for Objective-C types we need +typedef void NSString; +typedef void NSURL; +typedef void NSArray; + +const uint16_t MAX_STYLE_LENGTH = 128; // like "Bold Italic", so should never be big + +inline double round(double aNum) { + return aNum >= 0.0 ? std::floor(aNum + 0.5) : std::ceil(aNum - 0.5); +} + +// https://searchfox.org/firefox-main/rev/30ea9a2fd7271e9c731df414bd80e46edc3190eb/gfx/thebes/CoreTextFontList.cpp#770 +static uint32_t +convertWeight(float aCTWeight) { + constexpr std::pair kCoreTextToCSSWeights[] = { + {-1.0, 1}, + {-0.8, 100}, + {-0.6, 200}, + {-0.4, 300}, + {0.0, 400}, // standard 'regular' weight + {0.23, 500}, + {0.3, 600}, + {0.4, 700}, // standard 'bold' weight + {0.56, 800}, + {0.62, 900}, // Core Text seems to return 0.62 for faces with both + // usWeightClass=800 and 900 in their OS/2 tables! + // We use 900 as there are also fonts that return 0.56, + // so we want an intermediate value for that. + {1.0, 1000} + }; + const auto* begin = &kCoreTextToCSSWeights[0]; + const auto* end = begin + std::size(kCoreTextToCSSWeights); + auto m = std::upper_bound( + begin, + end, + aCTWeight, + [](CGFloat aValue, const std::pair& aMapping) { + return aValue <= aMapping.first; + } + ); + + if (m == end) return 1000; + if (m->first == aCTWeight || m == begin) return m->second; + // Interpolate between the preceding and found entries: + const auto* prev = m - 1; + const auto t = (aCTWeight - prev->first) / (m->first - prev->first); + return round(prev->second * (1.0 - t) + m->second * t); +} + +void +create_font_descriptor( + std::vector& results, + CTFontDescriptorRef descriptor +) { + FontDescriptor desc; + + NSURL *nsUrl = (NSURL *) CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute); + CFStringRef nsPath = CFURLCopyFileSystemPath((CFURLRef)nsUrl, kCFURLPOSIXPathStyle); + NSString *nsFamily = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute); + + // Should never happen, but shouldn't crash either + if (!nsUrl || !nsFamily) return; + + NSString *nsPostscript = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute); + NSString *nsStyle = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute); + CFDictionaryRef nsTraits = (CFDictionaryRef) CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute); + + // weight + if (nsTraits) { + CFNumberRef weightVal = (CFNumberRef) CFDictionaryGetValue(nsTraits, kCTFontWeightTrait); + float weightValue; + CFNumberGetValue(weightVal, kCFNumberFloatType, &weightValue); + desc.weight = (uint32_t) convertWeight(weightValue); + } + + // width + if (nsTraits) { + CFNumberRef widthVal = (CFNumberRef) CFDictionaryGetValue(nsTraits, kCTFontWidthTrait); + float widthValue; + CFNumberGetValue(widthVal, kCFNumberFloatType, &widthValue); + // https://searchfox.org/firefox-main/rev/cced10961b53e0d29e22e635404fec37728b2644/gfx/thebes/CoreTextFontList.cpp#819 + if (widthValue >= 0.0) { + desc.stretch = 100 + widthValue * 100; + } else { + desc.stretch = 100 + widthValue * 50; + } + } + + // file path + CFIndex pathLength = CFStringGetLength(nsPath) * 2 + 1; + desc.url = std::make_unique(pathLength); + CFStringGetCString(nsPath, desc.url.get(), pathLength, kCFStringEncodingUTF8); + + // family name + CFIndex familyLength = CFStringGetLength((CFStringRef)nsFamily) * 2 + 1; + std::unique_ptr family = std::make_unique(familyLength); + CFStringGetCString((CFStringRef)nsFamily, family.get(), familyLength, kCFStringEncodingUTF8); + desc.family = std::move(family); + + // postscript name + if (nsPostscript) { + CFIndex postscriptLength = CFStringGetLength((CFStringRef)nsPostscript) * 2 + 1; + std::unique_ptr postscript = std::make_unique(postscriptLength); + CFStringGetCString((CFStringRef)nsPostscript, postscript.get(), postscriptLength, kCFStringEncodingUTF8); + desc.postscript = std::move(postscript); + } + + // style + unsigned int symbolicTraits = 0; + if (nsTraits) { + CFNumberRef symbolicTraitsVal = (CFNumberRef)CFDictionaryGetValue(nsTraits, kCTFontSymbolicTrait); + CFNumberGetValue(symbolicTraitsVal, kCFNumberIntType, &symbolicTraits); + desc.style = FontStyle::Normal; + } + if (symbolicTraits & kCTFontItalicTrait) { + desc.style = FontStyle::Italic; + } else if (nsStyle) { + char styleBuffer[MAX_STYLE_LENGTH]; + CFStringGetCString((CFStringRef)nsStyle, styleBuffer, MAX_STYLE_LENGTH, kCFStringEncodingUTF8); + if (strstr(styleBuffer, "Oblique") != NULL) desc.style = FontStyle::Oblique; + } + + results.push_back(std::move(desc)); + + CFRelease(nsUrl); + CFRelease(nsPath); + CFRelease(nsFamily); + if (nsPostscript) CFRelease(nsPostscript); + if (nsStyle) CFRelease(nsStyle); + if (nsTraits) CFRelease(nsTraits); +} + +void +FontManagerMacos::readSystemFonts() { + static CTFontCollectionRef collection = NULL; + if (collection == NULL) collection = CTFontCollectionCreateFromAvailableFonts(NULL); + + NSArray *matches = (NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection); + CFIndex count = CFArrayGetCount((CFArrayRef) matches); + + system_fonts.reserve(count); + + for (CFIndex i = 0; i < count; i++) { + CTFontDescriptorRef match = (CTFontDescriptorRef)CFArrayGetValueAtIndex((CFArrayRef)matches, i); + create_font_descriptor(system_fonts, match); + } + + CFRelease(matches); +} + +void FontManagerMacos::populateFallbackFonts( + std::vector& families, + script_t script +) { + // Note: this was copied from Firefox, including comments, and tweaked to fit + switch (script) { + case SCRIPT_COMMON: + case SCRIPT_INHERITED: + // In most cases, COMMON and INHERITED characters will be merged into + // their context, but if they occur without any specific script context + // we'll just try common default fonts here. + case SCRIPT_LATIN: + case SCRIPT_CYRILLIC: + case SCRIPT_GREEK: + families.push_back("Lucida Grande"); + break; + + // CJK-related script codes are a bit troublesome because of unification; + // we'll probably just get HAN much of the time, so the choice of which + // language font to try for fallback is rather arbitrary. Usually, though, + // we hope that font prefs will have handled this earlier. + case SCRIPT_BOPOMOFO: + case SCRIPT_HAN: + families.push_back("Songti SC"); + families.push_back("SimSun-ExtB"); + break; + + case SCRIPT_HIRAGANA: + case SCRIPT_KATAKANA: + families.push_back("Hiragino Sans"); + families.push_back("Hiragino Kaku Gothic ProN"); + break; + + case SCRIPT_HANGUL: + families.push_back("Nanum Gothic"); + families.push_back("Apple SD Gothic Neo"); + break; + + // For most other scripts, macOS comes with a default font we can use. + case SCRIPT_ARABIC: + families.push_back("Geeza Pro"); + break; + case SCRIPT_ARMENIAN: + families.push_back("Mshtakan"); + break; + case SCRIPT_BENGALI: + families.push_back("Bangla Sangam MN"); + break; + case SCRIPT_CHEROKEE: + families.push_back("Plantagenet Cherokee"); + break; + case SCRIPT_COPTIC: + families.push_back("Noto Sans Coptic"); + break; + case SCRIPT_DESERET: + families.push_back("Baskerville"); + break; + case SCRIPT_DEVANAGARI: + families.push_back("Devanagari Sangam MN"); + break; + case SCRIPT_ETHIOPIC: + families.push_back("Kefa"); + break; + case SCRIPT_GEORGIAN: + families.push_back("Helvetica"); + break; + case SCRIPT_GOTHIC: + families.push_back("Noto Sans Gothic"); + break; + case SCRIPT_GUJARATI: + families.push_back("Gujarati Sangam MN"); + break; + case SCRIPT_GURMUKHI: + families.push_back("Gurmukhi MN"); + break; + case SCRIPT_HEBREW: + families.push_back("Lucida Grande"); + break; + case SCRIPT_KANNADA: + families.push_back("Kannada MN"); + break; + case SCRIPT_KHMER: + families.push_back("Khmer MN"); + break; + case SCRIPT_LAO: + families.push_back("Lao MN"); + break; + case SCRIPT_MALAYALAM: + families.push_back("Malayalam Sangam MN"); + break; + case SCRIPT_MONGOLIAN: + families.push_back("Noto Sans Mongolian"); + break; + case SCRIPT_MYANMAR: + families.push_back("Myanmar MN"); + break; + case SCRIPT_OGHAM: + families.push_back("Noto Sans Ogham"); + break; + case SCRIPT_OLD_ITALIC: + families.push_back("Noto Sans Old Italic"); + break; + case SCRIPT_ORIYA: + families.push_back("Oriya Sangam MN"); + break; + case SCRIPT_RUNIC: + families.push_back("Noto Sans Runic"); + break; + case SCRIPT_SINHALA: + families.push_back("Sinhala Sangam MN"); + break; + case SCRIPT_SYRIAC: + families.push_back("Noto Sans Syriac"); + break; + case SCRIPT_TAMIL: + families.push_back("Tamil MN"); + break; + case SCRIPT_TELUGU: + families.push_back("Telugu MN"); + break; + case SCRIPT_THAANA: + families.push_back("Noto Sans Thaana"); + break; + case SCRIPT_THAI: + families.push_back("Thonburi"); + break; + case SCRIPT_TIBETAN: + families.push_back("Kailasa"); + break; + case SCRIPT_CANADIAN_ABORIGINAL: + families.push_back("Euphemia UCAS"); + break; + case SCRIPT_YI: + families.push_back("Noto Sans Yi"); + families.push_back("STHeiti"); + break; + case SCRIPT_TAGALOG: + families.push_back("Noto Sans Tagalog"); + break; + case SCRIPT_HANUNOO: + families.push_back("Noto Sans Hanunoo"); + break; + case SCRIPT_BUHID: + families.push_back("Noto Sans Buhid"); + break; + case SCRIPT_TAGBANWA: + families.push_back("Noto Sans Tagbanwa"); + break; + case SCRIPT_BRAILLE: + families.push_back("Apple Braille"); + break; + case SCRIPT_CYPRIOT: + families.push_back("Noto Sans Cypriot"); + break; + case SCRIPT_LIMBU: + families.push_back("Noto Sans Limbu"); + break; + case SCRIPT_LINEAR_B: + families.push_back("Noto Sans Linear B"); + break; + case SCRIPT_OSMANYA: + families.push_back("Noto Sans Osmanya"); + break; + case SCRIPT_SHAVIAN: + families.push_back("Noto Sans Shavian"); + break; + case SCRIPT_TAI_LE: + families.push_back("Noto Sans Tai Le"); + break; + case SCRIPT_UGARITIC: + families.push_back("Noto Sans Ugaritic"); + break; + case SCRIPT_BUGINESE: + families.push_back("Noto Sans Buginese"); + break; + case SCRIPT_GLAGOLITIC: + families.push_back("Noto Sans Glagolitic"); + break; + case SCRIPT_KHAROSHTHI: + families.push_back("Noto Sans Kharoshthi"); + break; + case SCRIPT_SYLOTI_NAGRI: + families.push_back("Noto Sans Syloti Nagri"); + break; + case SCRIPT_NEW_TAI_LUE: + families.push_back("Noto Sans New Tai Lue"); + break; + case SCRIPT_TIFINAGH: + families.push_back("Noto Sans Tifinagh"); + break; + case SCRIPT_OLD_PERSIAN: + families.push_back("Noto Sans Old Persian"); + break; + case SCRIPT_BALINESE: + families.push_back("Noto Sans Balinese"); + break; + case SCRIPT_BATAK: + families.push_back("Noto Sans Batak"); + break; + case SCRIPT_BRAHMI: + families.push_back("Noto Sans Brahmi"); + break; + case SCRIPT_CHAM: + families.push_back("Noto Sans Cham"); + break; + case SCRIPT_EGYPTIAN_HIEROGLYPHS: + families.push_back("Noto Sans Egyptian Hieroglyphs"); + break; + case SCRIPT_PAHAWH_HMONG: + families.push_back("Noto Sans Pahawh Hmong"); + break; + case SCRIPT_OLD_HUNGARIAN: + families.push_back("Noto Sans Old Hungarian"); + break; + case SCRIPT_JAVANESE: + families.push_back("Noto Sans Javanese"); + break; + case SCRIPT_KAYAH_LI: + families.push_back("Noto Sans Kayah Li"); + break; + case SCRIPT_LEPCHA: + families.push_back("Noto Sans Lepcha"); + break; + case SCRIPT_LINEAR_A: + families.push_back("Noto Sans Linear A"); + break; + case SCRIPT_MANDAIC: + families.push_back("Noto Sans Mandaic"); + break; + case SCRIPT_NKO: + families.push_back("Noto Sans NKo"); + break; + case SCRIPT_OLD_TURKIC: + families.push_back("Noto Sans Old Turkic"); + break; + case SCRIPT_OLD_PERMIC: + families.push_back("Noto Sans Old Permic"); + break; + case SCRIPT_PHAGS_PA: + families.push_back("Noto Sans PhagsPa"); + break; + case SCRIPT_PHOENICIAN: + families.push_back("Noto Sans Phoenician"); + break; + case SCRIPT_MIAO: + families.push_back("Noto Sans Miao"); + break; + case SCRIPT_VAI: + families.push_back("Noto Sans Vai"); + break; + case SCRIPT_CUNEIFORM: + families.push_back("Noto Sans Cuneiform"); + break; + case SCRIPT_CARIAN: + families.push_back("Noto Sans Carian"); + break; + case SCRIPT_TAI_THAM: + families.push_back("Noto Sans Tai Tham"); + break; + case SCRIPT_LYCIAN: + families.push_back("Noto Sans Lycian"); + break; + case SCRIPT_LYDIAN: + families.push_back("Noto Sans Lydian"); + break; + case SCRIPT_OL_CHIKI: + families.push_back("Noto Sans Ol Chiki"); + break; + case SCRIPT_REJANG: + families.push_back("Noto Sans Rejang"); + break; + case SCRIPT_SAURASHTRA: + families.push_back("Noto Sans Saurashtra"); + break; + case SCRIPT_SUNDANESE: + families.push_back("Noto Sans Sundanese"); + break; + case SCRIPT_MEETEI_MAYEK: + families.push_back("Noto Sans Meetei Mayek"); + break; + case SCRIPT_IMPERIAL_ARAMAIC: + families.push_back("Noto Sans Imperial Aramaic"); + break; + case SCRIPT_AVESTAN: + families.push_back("Noto Sans Avestan"); + break; + case SCRIPT_CHAKMA: + families.push_back("Noto Sans Chakma"); + break; + case SCRIPT_KAITHI: + families.push_back("Noto Sans Kaithi"); + break; + case SCRIPT_MANICHAEAN: + families.push_back("Noto Sans Manichaean"); + break; + case SCRIPT_INSCRIPTIONAL_PAHLAVI: + families.push_back("Noto Sans Inscriptional Pahlavi"); + break; + case SCRIPT_PSALTER_PAHLAVI: + families.push_back("Noto Sans Psalter Pahlavi"); + break; + case SCRIPT_INSCRIPTIONAL_PARTHIAN: + families.push_back("Noto Sans Inscriptional Parthian"); + break; + case SCRIPT_SAMARITAN: + families.push_back("Noto Sans Samaritan"); + break; + case SCRIPT_TAI_VIET: + families.push_back("Noto Sans Tai Viet"); + break; + case SCRIPT_BAMUM: + families.push_back("Noto Sans Bamum"); + break; + case SCRIPT_LISU: + families.push_back("Noto Sans Lisu"); + break; + case SCRIPT_OLD_SOUTH_ARABIAN: + families.push_back("Noto Sans Old South Arabian"); + break; + case SCRIPT_BASSA_VAH: + families.push_back("Noto Sans Bassa Vah"); + break; + case SCRIPT_DUPLOYAN: + families.push_back("Noto Sans Duployan"); + break; + case SCRIPT_ELBASAN: + families.push_back("Noto Sans Elbasan"); + break; + case SCRIPT_GRANTHA: + families.push_back("Noto Sans Grantha"); + break; + case SCRIPT_MENDE_KIKAKUI: + families.push_back("Noto Sans Mende Kikakui"); + break; + case SCRIPT_MEROITIC_CURSIVE: + case SCRIPT_MEROITIC_HIEROGLYPHS: + families.push_back("Noto Sans Meroitic"); + break; + case SCRIPT_OLD_NORTH_ARABIAN: + families.push_back("Noto Sans Old North Arabian"); + break; + case SCRIPT_NABATAEAN: + families.push_back("Noto Sans Nabataean"); + break; + case SCRIPT_PALMYRENE: + families.push_back("Noto Sans Palmyrene"); + break; + case SCRIPT_KHUDAWADI: + families.push_back("Noto Sans Khudawadi"); + break; + case SCRIPT_WARANG_CITI: + families.push_back("Noto Sans Warang Citi"); + break; + case SCRIPT_MRO: + families.push_back("Noto Sans Mro"); + break; + case SCRIPT_SHARADA: + families.push_back("Noto Sans Sharada"); + break; + case SCRIPT_SORA_SOMPENG: + families.push_back("Noto Sans Sora Sompeng"); + break; + case SCRIPT_TAKRI: + families.push_back("Noto Sans Takri"); + break; + case SCRIPT_KHOJKI: + families.push_back("Noto Sans Khojki"); + break; + case SCRIPT_TIRHUTA: + families.push_back("Noto Sans Tirhuta"); + break; + case SCRIPT_CAUCASIAN_ALBANIAN: + families.push_back("Noto Sans Caucasian Albanian"); + break; + case SCRIPT_MAHAJANI: + families.push_back("Noto Sans Mahajani"); + break; + case SCRIPT_AHOM: + families.push_back("Noto Serif Ahom"); + break; + case SCRIPT_HATRAN: + families.push_back("Noto Sans Hatran"); + break; + case SCRIPT_MODI: + families.push_back("Noto Sans Modi"); + break; + case SCRIPT_MULTANI: + families.push_back("Noto Sans Multani"); + break; + case SCRIPT_PAU_CIN_HAU: + families.push_back("Noto Sans Pau Cin Hau"); + break; + case SCRIPT_SIDDHAM: + families.push_back("Noto Sans Siddham"); + break; + case SCRIPT_ADLAM: + families.push_back("Noto Sans Adlam"); + break; + case SCRIPT_BHAIKSUKI: + families.push_back("Noto Sans Bhaiksuki"); + break; + case SCRIPT_MARCHEN: + families.push_back("Noto Sans Marchen"); + break; + case SCRIPT_NEWA: + families.push_back("Noto Sans Newa"); + break; + case SCRIPT_OSAGE: + families.push_back("Noto Sans Osage"); + break; + case SCRIPT_HANIFI_ROHINGYA: + families.push_back("Noto Sans Hanifi Rohingya"); + break; + case SCRIPT_WANCHO: + families.push_back("Noto Sans Wancho"); + break; + + // Script codes for which no commonly-installed font is currently known. + // Probably future macOS versions will add Noto fonts for many of these, + // so we should watch for updates. + case SCRIPT_NONE: + case SCRIPT_NUSHU: + case SCRIPT_TANGUT: + case SCRIPT_ANATOLIAN_HIEROGLYPHS: + case SCRIPT_MASARAM_GONDI: + case SCRIPT_SOYOMBO: + case SCRIPT_ZANABAZAR_SQUARE: + case SCRIPT_DOGRA: + case SCRIPT_GUNJALA_GONDI: + case SCRIPT_MAKASAR: + case SCRIPT_MEDEFAIDRIN: + case SCRIPT_SOGDIAN: + case SCRIPT_OLD_SOGDIAN: + case SCRIPT_ELYMAIC: + case SCRIPT_NYIAKENG_PUACHUE_HMONG: + case SCRIPT_NANDINAGARI: + case SCRIPT_CHORASMIAN: + case SCRIPT_DIVES_AKURU: + case SCRIPT_KHITAN_SMALL_SCRIPT: + case SCRIPT_YEZIDI: + case SCRIPT_CYPRO_MINOAN: + case SCRIPT_OLD_UYGHUR: + case SCRIPT_TANGSA: + case SCRIPT_TOTO: + case SCRIPT_VITHKUQI: + case SCRIPT_KAWI: + case SCRIPT_NAG_MUNDARI: + case SCRIPT_GARAY: + case SCRIPT_GURUNG_KHEMA: + case SCRIPT_KIRAT_RAI: + case SCRIPT_OL_ONAL: + case SCRIPT_SIGNWRITING: + case SCRIPT_SUNUWAR: + case SCRIPT_TODHRI: + case SCRIPT_TULU_TIGALARI: + break; + } + + // TODO: Color Emoji should depend on if the default presentation for the + // codepoint is color or if a VS16 selector is present. + + families.push_back("Apple Color Emoji"); + + // TODO: Firefox makes the middle these 6 conditional on the codepoint. + // When users try to paint text that isn't in the first few families, this + // is going to be slower than it needs to be. Original Firefox comment next... + // + // Symbols/dingbats are generally Script=COMMON but may be resolved to any + // surrounding script run. So we'll always append a couple of likely fonts + // for such characters. + families.push_back("Zapf Dingbats"); + families.push_back("Geneva"); + families.push_back("STIXGeneral"); + families.push_back("Apple Symbols"); + // Japanese fonts also cover a lot of miscellaneous symbols + families.push_back("Hiragino Sans"); + families.push_back("Hiragino Kaku Gothic ProN"); + + // Arial Unicode MS has lots of glyphs for obscure characters; try it as a + // last resort. + families.push_back("Arial Unicode MS"); +} + +// See the preferences font.name-list.*.x-western in Firefox +const std::vector serif_fonts = {"Times", "Times New Roman"}; +const std::vector sans_serif_fonts = {"Helvetica", "Arial"}; +const std::vector monospace_fonts = {"Menlo"}; +const std::vector cursive_fonts = {"Apple Chancery"}; +const std::vector fantasy_fonts = {"Papyrus"}; + +std::optional*> +FontManagerMacos::getGenericList(const std::string& generic) { + if (generic == "serif") { + return &serif_fonts; + } else if (generic == "sans-serif") { + return &sans_serif_fonts; + } else if (generic == "monospace") { + return &monospace_fonts; + } else if (generic == "cursive") { + return &cursive_fonts; + } else if (generic == "fantasy") { + return &fantasy_fonts; + } else { + return std::nullopt; + } +} diff --git a/src/FontManagerMacos.h b/src/FontManagerMacos.h new file mode 100644 index 000000000..9f8578dcb --- /dev/null +++ b/src/FontManagerMacos.h @@ -0,0 +1,11 @@ +#pragma once + +#include "FontManager.h" +#include + +class FontManagerMacos : public FontManager { + public: + void readSystemFonts() override; + void populateFallbackFonts(std::vector& families, script_t script) override; + std::optional*> getGenericList(const std::string& generic) override; +}; diff --git a/src/FontManagerWindows.cc b/src/FontManagerWindows.cc new file mode 100644 index 000000000..5f6f40edc --- /dev/null +++ b/src/FontManagerWindows.cc @@ -0,0 +1,648 @@ +// Copyright (c) 2025 Caleb Hearon +// +// References: +// - https://searchfox.org/firefox-main/rev/e28b34ab33dbf49364999070168cbb7e11e8e5bd/gfx/thebes/gfxDWriteFontList.cpp +// - https://searchfox.org/firefox-main/rev/e28b34ab33dbf49364999070168cbb7e11e8e5bd/gfx/thebes/gfxWindowsPlatform.cpp + +#include +#include + +#include +#include +#include +#include + +#include "FontManagerWindows.h" +#include "Font.h" +#include "unicode.h" + +// RAII helper to release a COM interface on scope exit. +template +struct ComPtr { + T* ptr = nullptr; + ComPtr() = default; + ComPtr(const ComPtr&) = delete; + ComPtr& operator=(const ComPtr&) = delete; + ~ComPtr() { if (ptr) ptr->Release(); } + T** operator&() { return &ptr; } + T* operator->() const { return ptr; } + explicit operator bool() const { return ptr != nullptr; } +}; + +static std::unique_ptr +utf16ToUtf8(const wchar_t* wstr) { + int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); + if (len <= 0) return nullptr; + + auto buffer = std::make_unique(len); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buffer.get(), len, nullptr, nullptr); + return buffer; +} + +static std::unique_ptr +getEnUsString(IDWriteLocalizedStrings* strings) { + UINT32 index = 0; + BOOL exists = FALSE; + if (FAILED(strings->FindLocaleName(L"en-US", &index, &exists)) || !exists) { + return nullptr; + } + + UINT32 length = 0; + if (FAILED(strings->GetStringLength(index, &length))) return nullptr; + + auto wbuffer = std::make_unique(length + 1); + if (FAILED(strings->GetString(index, wbuffer.get(), length + 1))) return nullptr; + + return utf16ToUtf8(wbuffer.get()); +} + +static std::unique_ptr +getFontFilePath(IDWriteFontFile* file) { + if (!file) return nullptr; + + const void* referenceKey = nullptr; + UINT32 referenceKeySize = 0; + HRESULT res1 = file->GetReferenceKey(&referenceKey, &referenceKeySize); + if (FAILED(res1)) return nullptr; + + ComPtr loader; + HRESULT res2 = file->GetLoader(&loader); + if (FAILED(res2)) return nullptr; + + ComPtr localLoader; + HRESULT res3 = loader->QueryInterface( + __uuidof(IDWriteLocalFontFileLoader), + (void**)&localLoader + ); + if (FAILED(res3)) return nullptr; + + UINT32 pathLength = 0; + HRESULT res4 = localLoader->GetFilePathLengthFromKey( + referenceKey, + referenceKeySize, + &pathLength + ); + if (FAILED(res4)) return nullptr; + + auto wpath = std::make_unique(pathLength + 1); + HRESULT res5 = localLoader->GetFilePathFromKey( + referenceKey, + referenceKeySize, + wpath.get(), + pathLength + 1 + ); + if (FAILED(res5)) return nullptr; + + return wpath; +} + +static uint16_t +convertStretch(DWRITE_FONT_STRETCH stretch) { + switch (stretch) { + case DWRITE_FONT_STRETCH_ULTRA_CONDENSED: return 50; + case DWRITE_FONT_STRETCH_EXTRA_CONDENSED: return 63; // 62.5 + case DWRITE_FONT_STRETCH_CONDENSED: return 75; + case DWRITE_FONT_STRETCH_SEMI_CONDENSED: return 88; // 87.5 + case DWRITE_FONT_STRETCH_SEMI_EXPANDED: return 113; // 112.5 + case DWRITE_FONT_STRETCH_EXPANDED: return 125; + case DWRITE_FONT_STRETCH_EXTRA_EXPANDED: return 150; + case DWRITE_FONT_STRETCH_ULTRA_EXPANDED: return 200; + case DWRITE_FONT_STRETCH_NORMAL: + case DWRITE_FONT_STRETCH_UNDEFINED: + default: return 100; + } +} + +static FontStyle +convertStyle(DWRITE_FONT_STYLE style) { + switch (style) { + case DWRITE_FONT_STYLE_ITALIC: return FontStyle::Italic; + case DWRITE_FONT_STYLE_OBLIQUE: return FontStyle::Oblique; + case DWRITE_FONT_STYLE_NORMAL: + default: return FontStyle::Normal; + } +} + +static void +create_font_descriptor( + std::vector& results, + IDWriteFont* font, + const char* familyName +) { + ComPtr face; + if (FAILED(font->CreateFontFace(&face))) return; + + UINT32 numberOfFiles = 1; + if (FAILED(face->GetFiles(&numberOfFiles, nullptr))) return; + if (numberOfFiles != 1) return; + + IDWriteFontFile* file; + if (FAILED(face->GetFiles(&numberOfFiles, &file))) return; + + std::unique_ptr path = getFontFilePath(file); + file->Release(); + if (!path) return; + + FontDescriptor desc; + desc.url = std::move(path); + + size_t familyLength = std::strlen(familyName) + 1; + desc.family = std::make_unique(familyLength); + std::memcpy(desc.family.get(), familyName, familyLength); + desc.weight = static_cast(font->GetWeight()); + desc.stretch = convertStretch(font->GetStretch()); + desc.style = convertStyle(font->GetStyle()); + desc.index = face->GetIndex(); + + results.push_back(std::move(desc)); +} + +void +FontManagerWindows::readSystemFonts() { + ComPtr factory; + HRESULT res = DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + (IUnknown**)&factory + ); + + if (FAILED(res)) return; + + ComPtr collection; + if (FAILED(factory->GetSystemFontCollection(&collection, FALSE))) return; + + UINT32 familyCount = collection->GetFontFamilyCount(); + + for (UINT32 i = 0; i < familyCount; i++) { + ComPtr family; + if (FAILED(collection->GetFontFamily(i, &family))) continue; + + ComPtr familyNames; + if (FAILED(family->GetFamilyNames(&familyNames))) continue; + + std::unique_ptr familyName = getEnUsString(familyNames.ptr); + if (!familyName) continue; + + UINT32 fontCount = family->GetFontCount(); + for (UINT32 j = 0; j < fontCount; j++) { + ComPtr font; + if (FAILED(family->GetFont(j, &font))) continue; + create_font_descriptor(system_fonts, font.ptr, familyName.get()); + } + } +} + +void +FontManagerWindows::populateFallbackFonts( + std::vector& families, + script_t script +) { + // Note: this was copied from Firefox, including comments, and tweaked to fit + switch (script) { + case SCRIPT_COMMON: + case SCRIPT_INHERITED: + // In most cases, COMMON and INHERITED characters will be merged into + // their context, but if they occur without context, we'll just treat + // them like Latin, etc. + case SCRIPT_LATIN: + case SCRIPT_CYRILLIC: + case SCRIPT_GREEK: + case SCRIPT_ARMENIAN: + case SCRIPT_HEBREW: + // families.push_back("Arial"); + break; + + // CJK-related script codes are a bit troublesome because of unification; + // we'll probably just get HAN much of the time, so the choice of which + // language font to try for fallback is rather arbitrary. Usually, though, + // we hope that font prefs will have handled this earlier. + case SCRIPT_BOPOMOFO: + case SCRIPT_HAN: + families.push_back("SimSun"); + // We can't see the codepoint here, so always offer the ext-B font too. + families.push_back("SimSun-ExtB"); + break; + case SCRIPT_HIRAGANA: + case SCRIPT_KATAKANA: + families.push_back("Yu Gothic"); + families.push_back("MS PGothic"); + break; + case SCRIPT_HANGUL: + families.push_back("Malgun Gothic"); + break; + + case SCRIPT_YI: + families.push_back("Microsoft Yi Baiti"); + break; + case SCRIPT_MONGOLIAN: + families.push_back("Mongolian Baiti"); + break; + case SCRIPT_TIBETAN: + families.push_back("Microsoft Himalaya"); + break; + case SCRIPT_PHAGS_PA: + families.push_back("Microsoft PhagsPa"); + break; + + case SCRIPT_ARABIC: + // Default to Arial (added unconditionally below) for Arabic script. + break; + case SCRIPT_SYRIAC: + families.push_back("Estrangelo Edessa"); + break; + case SCRIPT_THAANA: + families.push_back("MV Boli"); + break; + + case SCRIPT_BENGALI: + families.push_back("Vrinda"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_DEVANAGARI: + families.push_back("Kokila"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_GUJARATI: + families.push_back("Shruti"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_GURMUKHI: + families.push_back("Raavi"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_KANNADA: + families.push_back("Tunga"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_MALAYALAM: + families.push_back("Kartika"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_ORIYA: + families.push_back("Kalinga"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_TAMIL: + families.push_back("Latha"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_TELUGU: + families.push_back("Gautami"); + families.push_back("Nirmala UI"); + break; + case SCRIPT_SINHALA: + families.push_back("Iskoola Pota"); + families.push_back("Nirmala UI"); + break; + + case SCRIPT_CHAKMA: + case SCRIPT_MEETEI_MAYEK: + case SCRIPT_OL_CHIKI: + case SCRIPT_SORA_SOMPENG: + families.push_back("Nirmala UI"); + break; + + case SCRIPT_MYANMAR: + families.push_back("Myanmar Text"); + break; + case SCRIPT_KHMER: + families.push_back("Khmer UI"); + break; + case SCRIPT_LAO: + families.push_back("Lao UI"); + break; + case SCRIPT_THAI: + families.push_back("Tahoma"); + families.push_back("Leelawadee UI"); + break; + case SCRIPT_TAI_LE: + families.push_back("Microsoft Tai Le"); + break; + case SCRIPT_BUGINESE: + families.push_back("Leelawadee UI"); + break; + case SCRIPT_NEW_TAI_LUE: + families.push_back("Microsoft New Tai Lue"); + break; + case SCRIPT_JAVANESE: + families.push_back("Javanese Text"); + break; + + case SCRIPT_GEORGIAN: + case SCRIPT_LISU: + families.push_back("Segoe UI"); + break; + + case SCRIPT_ETHIOPIC: + families.push_back("Nyala"); + families.push_back("Ebrima"); + break; + + case SCRIPT_ADLAM: + case SCRIPT_NKO: + case SCRIPT_OSMANYA: + case SCRIPT_TIFINAGH: + case SCRIPT_VAI: + families.push_back("Ebrima"); + break; + + case SCRIPT_CANADIAN_ABORIGINAL: + families.push_back("Euphemia"); + break; + + case SCRIPT_CHEROKEE: + case SCRIPT_OSAGE: + families.push_back("Gadugi"); + break; + + case SCRIPT_BRAILLE: + case SCRIPT_DESERET: + families.push_back("Segoe UI Symbol"); + break; + + case SCRIPT_BRAHMI: + case SCRIPT_CARIAN: + case SCRIPT_CUNEIFORM: + case SCRIPT_CYPRIOT: + case SCRIPT_EGYPTIAN_HIEROGLYPHS: + case SCRIPT_GLAGOLITIC: + case SCRIPT_GOTHIC: + case SCRIPT_IMPERIAL_ARAMAIC: + case SCRIPT_INSCRIPTIONAL_PAHLAVI: + case SCRIPT_INSCRIPTIONAL_PARTHIAN: + case SCRIPT_KHAROSHTHI: + case SCRIPT_LYCIAN: + case SCRIPT_LYDIAN: + case SCRIPT_MEROITIC_CURSIVE: + case SCRIPT_OGHAM: + case SCRIPT_OLD_ITALIC: + case SCRIPT_OLD_PERSIAN: + case SCRIPT_OLD_SOUTH_ARABIAN: + case SCRIPT_OLD_TURKIC: + case SCRIPT_PHOENICIAN: + case SCRIPT_RUNIC: + case SCRIPT_SHAVIAN: + case SCRIPT_UGARITIC: + families.push_back("Segoe UI Historic"); + break; + + // For some scripts where Windows doesn't supply a font by default, + // there are Noto fonts that users might have installed: + case SCRIPT_AHOM: + families.push_back("Noto Serif Ahom"); + break; + case SCRIPT_AVESTAN: + families.push_back("Noto Sans Avestan"); + break; + case SCRIPT_BALINESE: + families.push_back("Noto Sans Balinese"); + break; + case SCRIPT_BAMUM: + families.push_back("Noto Sans Bamum"); + break; + case SCRIPT_BASSA_VAH: + families.push_back("Noto Sans Bassa Vah"); + break; + case SCRIPT_BATAK: + families.push_back("Noto Sans Batak"); + break; + case SCRIPT_BHAIKSUKI: + families.push_back("Noto Sans Bhaiksuki"); + break; + case SCRIPT_BUHID: + families.push_back("Noto Sans Buhid"); + break; + case SCRIPT_CAUCASIAN_ALBANIAN: + families.push_back("Noto Sans Caucasian Albanian"); + break; + case SCRIPT_CHAM: + families.push_back("Noto Sans Cham"); + break; + case SCRIPT_COPTIC: + families.push_back("Noto Sans Coptic"); + break; + case SCRIPT_DUPLOYAN: + families.push_back("Noto Sans Duployan"); + break; + case SCRIPT_ELBASAN: + families.push_back("Noto Sans Elbasan"); + break; + case SCRIPT_GRANTHA: + families.push_back("Noto Sans Grantha"); + break; + case SCRIPT_HANIFI_ROHINGYA: + families.push_back("Noto Sans Hanifi Rohingya"); + break; + case SCRIPT_HANUNOO: + families.push_back("Noto Sans Hanunoo"); + break; + case SCRIPT_HATRAN: + families.push_back("Noto Sans Hatran"); + break; + case SCRIPT_KAITHI: + families.push_back("Noto Sans Kaithi"); + break; + case SCRIPT_KAYAH_LI: + families.push_back("Noto Sans Kayah Li"); + break; + case SCRIPT_KHOJKI: + families.push_back("Noto Sans Khojki"); + break; + case SCRIPT_KHUDAWADI: + families.push_back("Noto Sans Khudawadi"); + break; + case SCRIPT_LEPCHA: + families.push_back("Noto Sans Lepcha"); + break; + case SCRIPT_LIMBU: + families.push_back("Noto Sans Limbu"); + break; + case SCRIPT_LINEAR_A: + families.push_back("Noto Sans Linear A"); + break; + case SCRIPT_LINEAR_B: + families.push_back("Noto Sans Linear B"); + break; + case SCRIPT_MAHAJANI: + families.push_back("Noto Sans Mahajani"); + break; + case SCRIPT_MANDAIC: + families.push_back("Noto Sans Mandaic"); + break; + case SCRIPT_MANICHAEAN: + families.push_back("Noto Sans Manichaean"); + break; + case SCRIPT_MARCHEN: + families.push_back("Noto Sans Marchen"); + break; + case SCRIPT_MENDE_KIKAKUI: + families.push_back("Noto Sans Mende Kikakui"); + break; + case SCRIPT_MEROITIC_HIEROGLYPHS: + families.push_back("Noto Sans Meroitic"); + break; + case SCRIPT_MIAO: + families.push_back("Noto Sans Miao"); + break; + case SCRIPT_MODI: + families.push_back("Noto Sans Modi"); + break; + case SCRIPT_MRO: + families.push_back("Noto Sans Mro"); + break; + case SCRIPT_MULTANI: + families.push_back("Noto Sans Multani"); + break; + case SCRIPT_NABATAEAN: + families.push_back("Noto Sans Nabataean"); + break; + case SCRIPT_NEWA: + families.push_back("Noto Sans Newa"); + break; + case SCRIPT_OLD_HUNGARIAN: + families.push_back("Noto Sans Old Hungarian"); + break; + case SCRIPT_OLD_NORTH_ARABIAN: + families.push_back("Noto Sans Old North Arabian"); + break; + case SCRIPT_OLD_PERMIC: + families.push_back("Noto Sans Old Permic"); + break; + case SCRIPT_PAHAWH_HMONG: + families.push_back("Noto Sans Pahawh Hmong"); + break; + case SCRIPT_PALMYRENE: + families.push_back("Noto Sans Palmyrene"); + break; + case SCRIPT_PAU_CIN_HAU: + families.push_back("Noto Sans Pau Cin Hau"); + break; + case SCRIPT_PSALTER_PAHLAVI: + families.push_back("Noto Sans Psalter Pahlavi"); + break; + case SCRIPT_REJANG: + families.push_back("Noto Sans Rejang"); + break; + case SCRIPT_SAMARITAN: + families.push_back("Noto Sans Samaritan"); + break; + case SCRIPT_SAURASHTRA: + families.push_back("Noto Sans Saurashtra"); + break; + case SCRIPT_SHARADA: + families.push_back("Noto Sans Sharada"); + break; + case SCRIPT_SIDDHAM: + families.push_back("Noto Sans Siddham"); + break; + case SCRIPT_SUNDANESE: + families.push_back("Noto Sans Sundanese"); + break; + case SCRIPT_SYLOTI_NAGRI: + families.push_back("Noto Sans Syloti Nagri"); + break; + case SCRIPT_TAGALOG: + families.push_back("Noto Sans Tagalog"); + break; + case SCRIPT_TAGBANWA: + families.push_back("Noto Sans Tagbanwa"); + break; + case SCRIPT_TAI_THAM: + families.push_back("Noto Sans Tai Tham"); + break; + case SCRIPT_TAI_VIET: + families.push_back("Noto Sans Tai Viet"); + break; + case SCRIPT_TAKRI: + families.push_back("Noto Sans Takri"); + break; + case SCRIPT_TIRHUTA: + families.push_back("Noto Sans Tirhuta"); + break; + case SCRIPT_WANCHO: + families.push_back("Noto Sans Wancho"); + break; + case SCRIPT_WARANG_CITI: + families.push_back("Noto Sans Warang Citi"); + break; + + case SCRIPT_NONE: + case SCRIPT_ANATOLIAN_HIEROGLYPHS: + case SCRIPT_CHORASMIAN: + case SCRIPT_CYPRO_MINOAN: + case SCRIPT_DIVES_AKURU: + case SCRIPT_DOGRA: + case SCRIPT_ELYMAIC: + case SCRIPT_GARAY: + case SCRIPT_GUNJALA_GONDI: + case SCRIPT_GURUNG_KHEMA: + case SCRIPT_KAWI: + case SCRIPT_KHITAN_SMALL_SCRIPT: + case SCRIPT_KIRAT_RAI: + case SCRIPT_MAKASAR: + case SCRIPT_MASARAM_GONDI: + case SCRIPT_MEDEFAIDRIN: + case SCRIPT_NAG_MUNDARI: + case SCRIPT_NANDINAGARI: + case SCRIPT_NUSHU: + case SCRIPT_NYIAKENG_PUACHUE_HMONG: + case SCRIPT_OL_ONAL: + case SCRIPT_OLD_SOGDIAN: + case SCRIPT_OLD_UYGHUR: + case SCRIPT_SIGNWRITING: + case SCRIPT_SOGDIAN: + case SCRIPT_SOYOMBO: + case SCRIPT_SUNUWAR: + case SCRIPT_TANGSA: + case SCRIPT_TANGUT: + case SCRIPT_TODHRI: + case SCRIPT_TOTO: + case SCRIPT_TULU_TIGALARI: + case SCRIPT_VITHKUQI: + case SCRIPT_YEZIDI: + case SCRIPT_ZANABAZAR_SQUARE: + break; + } + + // Arial is used as default fallback for system fallback, so always try that. + families.push_back("Arial"); + + // TODO: Color Emoji should depend on if the default presentation for the + // codepoint is color or if a VS16 selector is present. For now we always + // prefer it (Firefox's PrefersColor() path). + families.push_back("Segoe UI Emoji"); + families.push_back("Twemoji Mozilla"); + + // Symbols/dingbats are generally Script=COMMON but may be resolved to any + // surrounding script run. So we'll always append a couple of likely fonts + // for such characters. (Firefox gates these on the codepoint, which we don't + // thread through here, so we add them unconditionally.) + families.push_back("Segoe UI"); + families.push_back("Segoe UI Symbol"); + families.push_back("Cambria Math"); + + // Arial Unicode MS also has lots of glyphs for obscure characters; try it as + // a last resort, if available. + families.push_back("Arial Unicode MS"); +} + +// See the preferences font.name-list.*.x-western in Firefox (Windows values). +const std::vector serif_fonts = {"Times New Roman"}; +const std::vector sans_serif_fonts = {"Arial"}; +const std::vector monospace_fonts = {"Consolas"}; +const std::vector cursive_fonts = {"Comic Sans MS"}; + +std::optional*> +FontManagerWindows::getGenericList(const std::string& generic) { + if (generic == "serif") { + return &serif_fonts; + } else if (generic == "sans-serif") { + return &sans_serif_fonts; + } else if (generic == "monospace") { + return &monospace_fonts; + } else if (generic == "cursive") { + return &cursive_fonts; + } else { // Firefox doesn't list any Fantasy fonts for Windows + return std::nullopt; + } +} diff --git a/src/FontManagerWindows.h b/src/FontManagerWindows.h new file mode 100644 index 000000000..b5d397648 --- /dev/null +++ b/src/FontManagerWindows.h @@ -0,0 +1,11 @@ +#pragma once + +#include "FontManager.h" +#include + +class FontManagerWindows : public FontManager { + public: + void readSystemFonts() override; + void populateFallbackFonts(std::vector& families, script_t script) override; + std::optional*> getGenericList(const std::string& generic) override; +}; diff --git a/src/FontParser.cc b/src/FontParser.cc index 773502cb3..87620b246 100644 --- a/src/FontParser.cc +++ b/src/FontParser.cc @@ -402,15 +402,15 @@ FontParser::parseFontStyle(FontProperties& props) { if (check(Token::Type::Identifier)) { const auto& value = currentToken_.getString(); if (value == "italic") { - props.fontStyle = FontStyle::Italic; + props.style = FontStyle::Italic; advance(); return true; } else if (value == "oblique") { - props.fontStyle = FontStyle::Oblique; + props.style = FontStyle::Oblique; advance(); return true; } else if (value == "normal") { - props.fontStyle = FontStyle::Normal; + props.style = FontStyle::Normal; advance(); return true; } @@ -424,11 +424,11 @@ FontParser::parseFontVariant(FontProperties& props) { if (check(Token::Type::Identifier)) { const auto& value = currentToken_.getString(); if (value == "small-caps") { - props.fontVariant = FontVariant::SmallCaps; + props.variant = FontVariant::SmallCaps; advance(); return true; } else if (value == "normal") { - props.fontVariant = FontVariant::Normal; + props.variant = FontVariant::Normal; advance(); return true; } @@ -443,14 +443,14 @@ FontParser::parseFontWeight(FontProperties& props) { double weightFloat = currentToken_.getNumber(); int weight = static_cast(weightFloat); if (weight < 1 || weight > 1000) return false; - props.fontWeight = static_cast(weight); + props.weight = static_cast(weight); advance(); return true; } else if (check(Token::Type::Identifier)) { const auto& value = currentToken_.getString(); if (auto it = weightMap.find(value); it != weightMap.end()) { - props.fontWeight = it->second; + props.weight = it->second; advance(); return true; } @@ -463,7 +463,7 @@ bool FontParser::parseFontSize(FontProperties& props) { if (!check(Token::Type::Number)) return false; - props.fontSize = currentToken_.getNumber(); + props.size = currentToken_.getNumber(); advance(); double multiplier = 1.0f; @@ -487,7 +487,7 @@ FontParser::parseFontSize(FontProperties& props) { // we should rewind the tokenizer, but I don't think the grammar allows for // any valid alternates in this specific case - props.fontSize *= multiplier; + props.size *= multiplier; return true; } @@ -550,7 +550,7 @@ FontParser::parseFontFamily(FontProperties& props) { if (!found) return false; // only whitespace or non-id/string found - props.fontFamily.push_back(family); + props.families.push_back(family); if (check(Token::Type::Comma)) advance(); } @@ -566,6 +566,24 @@ FontParser::parse(const std::string& fontString, bool* success) { return result; } +std::optional +FontParser::parseWeight(const std::string& source) { + FontParser parser(source); + FontProperties props; // TODO: use only the memory needed + parser.skipWs(); + if (parser.parseFontWeight(props)) return props.weight; + return std::nullopt; +} + +std::optional +FontParser::parseStyle(const std::string& source) { + FontParser parser(source); + FontProperties props; // TODO: use only the memory needed + parser.skipWs(); + if (parser.parseFontStyle(props)) return props.style; + return std::nullopt; +} + FontProperties FontParser::parseFont() { FontProperties props; diff --git a/src/FontParser.h b/src/FontParser.h index c88802109..3b5604e3b 100644 --- a/src/FontParser.h +++ b/src/FontParser.h @@ -8,24 +8,7 @@ #include #include "CharData.h" -enum class FontStyle { - Normal, - Italic, - Oblique -}; - -enum class FontVariant { - Normal, - SmallCaps -}; - -struct FontProperties { - double fontSize{16.0f}; - std::vector fontFamily; - uint16_t fontWeight{400}; - FontVariant fontVariant{FontVariant::Normal}; - FontStyle fontStyle{FontStyle::Normal}; -}; +#include "Font.h" class Token { public: @@ -88,6 +71,8 @@ class Tokenizer { class FontParser { public: static FontProperties parse(const std::string& fontString, bool* success = nullptr); + static std::optional parseWeight(const std::string& source); + static std::optional parseStyle(const std::string& source); private: static const std::unordered_map weightMap; diff --git a/src/Image.cc b/src/Image.cc index 99dcb152b..2639989b5 100644 --- a/src/Image.cc +++ b/src/Image.cc @@ -8,7 +8,6 @@ #include #include #include -#include #include /* Cairo limit: @@ -16,68 +15,35 @@ */ static constexpr int canvas_max_side = (1 << 15) - 1; -#ifdef HAVE_GIF typedef struct { uint8_t *buf; unsigned len; unsigned pos; } gif_data_t; -#endif -#ifdef HAVE_JPEG #include struct canvas_jpeg_error_mgr: jpeg_error_mgr { - Image* image; + ImageSurface* surface; jmp_buf setjmp_buffer; }; -#endif /* * Read closure used by loadFromBuffer. */ typedef struct { - Napi::Env env; + std::optional env; unsigned len; uint8_t *buf; } read_closure_t; /* - * Initialize Image. + * Initialize a new ImageSurface (canvas_surface_t wrapper) */ -void -Image::Initialize(Napi::Env& env, Napi::Object& exports) { - InstanceData *data = env.GetInstanceData(); - - Napi::Function ctor = DefineClass(env, "Image", { - InstanceAccessor<&Image::GetComplete>("complete", napi_default_jsproperty), - InstanceAccessor<&Image::GetWidth, &Image::SetWidth>("width", napi_default_jsproperty), - InstanceAccessor<&Image::GetHeight, &Image::SetHeight>("height", napi_default_jsproperty), - InstanceAccessor<&Image::GetNaturalWidth>("naturalWidth", napi_default_jsproperty), - InstanceAccessor<&Image::GetNaturalHeight>("naturalHeight", napi_default_jsproperty), - InstanceAccessor<&Image::GetDataMode, &Image::SetDataMode>("dataMode", napi_default_jsproperty), - StaticValue("MODE_IMAGE", Napi::Number::New(env, DATA_IMAGE), napi_default_jsproperty), - StaticValue("MODE_MIME", Napi::Number::New(env, DATA_MIME), napi_default_jsproperty) - }); - - // Used internally in lib/image.js - exports.Set("GetSource", Napi::Function::New(env, &GetSource)); - exports.Set("SetSource", Napi::Function::New(env, &SetSource)); - - data->ImageCtor = Napi::Persistent(ctor); - exports.Set("Image", ctor); -} - -/* - * Initialize a new Image. - */ - -Image::Image(const Napi::CallbackInfo& info) : ObjectWrap(info), env(info.Env()) { +ImageSurface::ImageSurface(std::optional env) : env(env) { data_mode = DATA_IMAGE; - info.This().ToObject().Unwrap().Set("onload", env.Null()); - info.This().ToObject().Unwrap().Set("onerror", env.Null()); filename = NULL; _data = nullptr; _data_len = 0; @@ -85,109 +51,8 @@ Image::Image(const Napi::CallbackInfo& info) : ObjectWrap(info), env(info width = height = 0; naturalWidth = naturalHeight = 0; state = DEFAULT; -#ifdef HAVE_RSVG - _rsvg = NULL; - _is_svg = false; + svgdoc = nullptr; _svg_last_width = _svg_last_height = 0; -#endif -} - -/* - * Get complete boolean. - */ - -Napi::Value -Image::GetComplete(const Napi::CallbackInfo& info) { - return Napi::Boolean::New(env, true); -} - -/* - * Get dataMode. - */ - -Napi::Value -Image::GetDataMode(const Napi::CallbackInfo& info) { - return Napi::Number::New(env, data_mode); -} - -/* - * Set dataMode. - */ - -void -Image::SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value) { - if (value.IsNumber()) { - int mode = value.As().Uint32Value(); - data_mode = (data_mode_t) mode; - } -} - -/* - * Get natural width - */ - -Napi::Value -Image::GetNaturalWidth(const Napi::CallbackInfo& info) { - return Napi::Number::New(env, naturalWidth); -} - -/* - * Get width. - */ - -Napi::Value -Image::GetWidth(const Napi::CallbackInfo& info) { - return Napi::Number::New(env, width); -} - -/* - * Set width. - */ - -void -Image::SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value) { - if (value.IsNumber()) { - width = value.As().Uint32Value(); - } -} - -/* - * Get natural height - */ - -Napi::Value -Image::GetNaturalHeight(const Napi::CallbackInfo& info) { - return Napi::Number::New(env, naturalHeight); -} - -/* - * Get height. - */ - -Napi::Value -Image::GetHeight(const Napi::CallbackInfo& info) { - return Napi::Number::New(env, height); -} -/* - * Set height. - */ - -void -Image::SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value) { - if (value.IsNumber()) { - height = value.As().Uint32Value(); - } -} - -/* - * Get src path. - */ - -Napi::Value -Image::GetSource(const Napi::CallbackInfo& info){ - Napi::Env env = info.Env(); - Image *img = Image::Unwrap(info.This().As()); - return Napi::String::New(env, img->filename ? img->filename : ""); } /* @@ -195,10 +60,10 @@ Image::GetSource(const Napi::CallbackInfo& info){ */ void -Image::clearData() { +ImageSurface::clearData() { if (_surface) { cairo_surface_destroy(_surface); - Napi::MemoryManagement::AdjustExternalMemory(env, -_data_len); + if (env) Napi::MemoryManagement::AdjustExternalMemory(*env, -_data_len); _data_len = 0; _surface = NULL; } @@ -209,67 +74,23 @@ Image::clearData() { free(filename); filename = NULL; -#ifdef HAVE_RSVG - if (_rsvg != NULL) { - g_object_unref(_rsvg); - _rsvg = NULL; - } -#endif + svgdoc = nullptr; width = height = 0; naturalWidth = naturalHeight = 0; state = DEFAULT; } -/* - * Set src path. - */ - -void -Image::SetSource(const Napi::CallbackInfo& info){ - Napi::Env env = info.Env(); - Napi::Object This = info.This().As(); - Image *img = Image::Unwrap(This); - - cairo_status_t status = CAIRO_STATUS_READ_ERROR; - - Napi::Value value = info[0]; +cairo_surface_t* +ImageSurface::transferSurface() { + cairo_surface_t* surface = _surface; - img->clearData(); - // Clear errno in case some unrelated previous syscall failed - errno = 0; - - // url string - if (value.IsString()) { - std::string src = value.As().Utf8Value(); - if (img->filename) free(img->filename); - img->filename = strdup(src.c_str()); - status = img->load(); - // Buffer - } else if (value.IsBuffer()) { - uint8_t *buf = value.As>().Data(); - unsigned len = value.As>().Length(); - status = img->loadFromBuffer(buf, len); - } + if (env) Napi::MemoryManagement::AdjustExternalMemory(*env, -_data_len); + _data_len = 0; + _surface = nullptr; + _data = nullptr; - if (status) { - Napi::Value onerrorFn; - if (This.Get("onerror").UnwrapTo(&onerrorFn) && onerrorFn.IsFunction()) { - Napi::Error arg; - if (img->errorInfo.empty()) { - arg = Napi::Error::New(env, Napi::String::New(env, cairo_status_to_string(status))); - } else { - arg = img->errorInfo.toError(env); - } - onerrorFn.As().Call({ arg.Value() }); - } - } else { - img->loaded(); - Napi::Value onloadFn; - if (This.Get("onload").UnwrapTo(&onloadFn) && onloadFn.IsFunction()) { - onloadFn.As().Call({}); - } - } + return surface; } /* @@ -278,7 +99,9 @@ Image::SetSource(const Napi::CallbackInfo& info){ */ cairo_status_t -Image::loadFromBuffer(uint8_t *buf, unsigned len) { +ImageSurface::loadFromBuffer(uint8_t *buf, unsigned len) { + clearData(); + if (len == 0) return CAIRO_STATUS_READ_ERROR; uint8_t data[4] = {0}; @@ -287,16 +110,12 @@ Image::loadFromBuffer(uint8_t *buf, unsigned len) { if (isPNG(data)) return loadPNGFromBuffer(buf); if (isGIF(data)) { -#ifdef HAVE_GIF return loadGIFFromBuffer(buf, len); -#else this->errorInfo.set("node-canvas was built without GIF support"); return CAIRO_STATUS_READ_ERROR; -#endif } if (isJPEG(data)) { -#ifdef HAVE_JPEG if (DATA_IMAGE == data_mode) return loadJPEGFromBuffer(buf, len); if (DATA_MIME == data_mode) return decodeJPEGBufferIntoMimeSurface(buf, len); if ((DATA_IMAGE | DATA_MIME) == data_mode) { @@ -305,23 +124,13 @@ Image::loadFromBuffer(uint8_t *buf, unsigned len) { if (status) return status; return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG); } -#else // HAVE_JPEG - this->errorInfo.set("node-canvas was built without JPEG support"); - return CAIRO_STATUS_READ_ERROR; -#endif } // confirm svg using first 1000 chars // if a very long comment precedes the root tag, isSVG returns false unsigned head_len = (len < 1000 ? len : 1000); - if (isSVG(buf, head_len)) { -#ifdef HAVE_RSVG + if (isSVG(buf, head_len)) return loadSVGFromBuffer(buf, len); -#else - this->errorInfo.set("node-canvas was built without SVG support"); - return CAIRO_STATUS_READ_ERROR; -#endif - } if (isBMP(buf, len)) return loadBMPFromBuffer(buf, len); @@ -335,7 +144,7 @@ Image::loadFromBuffer(uint8_t *buf, unsigned len) { */ cairo_status_t -Image::loadPNGFromBuffer(uint8_t *buf) { +ImageSurface::loadPNGFromBuffer(uint8_t *buf) { read_closure_t closure{ env, 0, buf }; _surface = cairo_image_surface_create_from_png_stream(readPNG, &closure); cairo_status_t status = cairo_surface_status(_surface); @@ -348,7 +157,7 @@ Image::loadPNGFromBuffer(uint8_t *buf) { */ cairo_status_t -Image::readPNG(void *c, uint8_t *data, unsigned int len) { +ImageSurface::readPNG(void *c, uint8_t *data, unsigned int len) { read_closure_t *closure = (read_closure_t *) c; memcpy(data, closure->buf + closure->len, len); closure->len += len; @@ -359,7 +168,7 @@ Image::readPNG(void *c, uint8_t *data, unsigned int len) { * Destroy image and associated surface. */ -Image::~Image() { +ImageSurface::~ImageSurface() { clearData(); } @@ -368,7 +177,15 @@ Image::~Image() { */ cairo_status_t -Image::load() { +ImageSurface::load(std::string& filename) { + clearData(); + + // Clear errno in case some unrelated previous syscall failed + errno = 0; + + if (this->filename) free(this->filename); + this->filename = strdup(filename.c_str()); + if (LOADING != state) { state = LOADING; return loadSurface(); @@ -381,35 +198,26 @@ Image::load() { */ void -Image::loaded() { +ImageSurface::loaded() { state = COMPLETE; width = naturalWidth = cairo_image_surface_get_width(_surface); height = naturalHeight = cairo_image_surface_get_height(_surface); _data_len = naturalHeight * cairo_image_surface_get_stride(_surface); - Napi::MemoryManagement::AdjustExternalMemory(env, _data_len); + if (env) Napi::MemoryManagement::AdjustExternalMemory(*env, _data_len); } /* * Returns this image's surface. */ -cairo_surface_t *Image::surface() { -#ifdef HAVE_RSVG - if (_is_svg && (_svg_last_width != width || _svg_last_height != height)) { - if (_surface != NULL) { - cairo_surface_destroy(_surface); - _surface = NULL; - } - +cairo_surface_t *ImageSurface::surface() { + if (svgdoc && (_svg_last_width != width || _svg_last_height != height)) { cairo_status_t status = renderSVGToSurface(); if (status != CAIRO_STATUS_SUCCESS) { - g_object_unref(_rsvg); - Napi::Error::New(env, cairo_status_to_string(status)).ThrowAsJavaScriptException(); - - return NULL; + if (env) Napi::Error::New(*env, cairo_status_to_string(status)).ThrowAsJavaScriptException(); + return nullptr; } } -#endif return _surface; } @@ -421,7 +229,7 @@ cairo_surface_t *Image::surface() { */ cairo_status_t -Image::loadSurface() { +ImageSurface::loadSurface() { FILE *stream = fopen(filename, "rb"); if (!stream) { this->errorInfo.set(NULL, "fopen", errno, filename); @@ -442,21 +250,15 @@ Image::loadSurface() { if (isGIF(buf)) { -#ifdef HAVE_GIF return loadGIF(stream); -#else this->errorInfo.set("node-canvas was built without GIF support"); return CAIRO_STATUS_READ_ERROR; -#endif } if (isJPEG(buf)) { -#ifdef HAVE_JPEG return loadJPEG(stream); -#else this->errorInfo.set("node-canvas was built without JPEG support"); return CAIRO_STATUS_READ_ERROR; -#endif } // confirm svg using first 1000 chars @@ -473,12 +275,7 @@ Image::loadSurface() { } rewind(stream); if (isSVG(head, head_len)) { -#ifdef HAVE_RSVG return loadSVG(stream); -#else - this->errorInfo.set("node-canvas was built without SVG support"); - return CAIRO_STATUS_READ_ERROR; -#endif } if (isBMP(buf, 2)) @@ -495,15 +292,13 @@ Image::loadSurface() { */ cairo_status_t -Image::loadPNG() { +ImageSurface::loadPNG() { _surface = cairo_image_surface_create_from_png(filename); return cairo_surface_status(_surface); } // GIF support -#ifdef HAVE_GIF - /* * Return the alpha color for `gif` at `frame`, or -1. */ @@ -538,7 +333,7 @@ read_gif_from_memory(GifFileType *gif, GifByteType *buf, int len) { */ cairo_status_t -Image::loadGIF(FILE *stream) { +ImageSurface::loadGIF(FILE *stream) { struct stat s; int fd = fileno(stream); @@ -571,20 +366,15 @@ Image::loadGIF(FILE *stream) { */ cairo_status_t -Image::loadGIFFromBuffer(uint8_t *buf, unsigned len) { +ImageSurface::loadGIFFromBuffer(uint8_t *buf, unsigned len) { int i = 0; GifFileType* gif; gif_data_t gifd = { buf, len, 0 }; -#if GIFLIB_MAJOR >= 5 int errorcode; if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory, &errorcode)) == NULL) return CAIRO_STATUS_READ_ERROR; -#else - if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory)) == NULL) - return CAIRO_STATUS_READ_ERROR; -#endif if (GIF_OK != DGifSlurp(gif)) { GIF_CLOSE_FILE(gif); @@ -713,16 +503,9 @@ Image::loadGIFFromBuffer(uint8_t *buf, unsigned len) { return CAIRO_STATUS_SUCCESS; } -#endif /* HAVE_GIF */ // JPEG support -#ifdef HAVE_JPEG - -// libjpeg 6.2 does not have jpeg_mem_src; define it ourselves here unless -// libjpeg 8 is installed. -#if JPEG_LIB_VERSION < 80 && !defined(MEM_SRCDST_SUPPORTED) - /* Read JPEG image from a memory segment */ static void init_source(j_decompress_ptr cinfo) {} @@ -761,9 +544,7 @@ static void jpeg_mem_src (j_decompress_ptr cinfo, void* buffer, long nbytes) { src->next_input_byte = (JOCTET*)buffer; } -#endif - -class BufferReader : public Image::Reader { +class BufferReader : public ImageSurface::Reader { public: BufferReader(uint8_t* buf, unsigned len) : _buf(buf), _len(len), _idx(0) {} @@ -781,7 +562,7 @@ class BufferReader : public Image::Reader { unsigned _idx; }; -class StreamReader : public Image::Reader { +class StreamReader : public ImageSurface::Reader { public: StreamReader(FILE *stream) : _stream(stream), _len(0), _idx(0) { fseek(_stream, 0, SEEK_END); @@ -807,7 +588,7 @@ class StreamReader : public Image::Reader { unsigned _idx; }; -void Image::jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode) { +void ImageSurface::jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode) { int stride = naturalWidth * 4; for (int y = 0; y < naturalHeight; ++y) { jpeg_read_scanlines(args, &src, 1); @@ -825,7 +606,7 @@ void Image::jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src */ cairo_status_t -Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args, Orientation orientation) { +ImageSurface::decodeJPEGIntoSurface(jpeg_decompress_struct *args, Orientation orientation) { const int channels = 4; cairo_status_t status = CAIRO_STATUS_SUCCESS; @@ -922,7 +703,7 @@ static void canvas_jpeg_output_message(j_common_ptr cinfo) { char buff[JMSG_LENGTH_MAX]; cjerr->format_message(cinfo, buff); // (Only the last message will be returned to JS land.) - cjerr->image->errorInfo.set(buff); + cjerr->surface->errorInfo.set(buff); } /* @@ -931,13 +712,13 @@ static void canvas_jpeg_output_message(j_common_ptr cinfo) { */ cairo_status_t -Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) { +ImageSurface::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) { // TODO: remove this duplicate logic // JPEG setup struct jpeg_decompress_struct args; struct canvas_jpeg_error_mgr err; - err.image = this; + err.surface = this; args.err = jpeg_std_error(&err); args.err->error_exit = canvas_jpeg_error_exit; args.err->output_message = canvas_jpeg_output_message; @@ -1003,10 +784,9 @@ Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) { void clearMimeData(void *closure) { - Napi::MemoryManagement::AdjustExternalMemory( - static_cast(closure)->env, - -static_cast((static_cast(closure)->len))); - free(static_cast(closure)->buf); + read_closure_t* c = static_cast(closure); + if (c->env) Napi::MemoryManagement::AdjustExternalMemory(*c->env, (int)c->len); + free(c->buf); free(closure); } @@ -1017,7 +797,7 @@ clearMimeData(void *closure) { */ cairo_status_t -Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) { +ImageSurface::assignDataAsMime(uint8_t *data, int len, const char *mime_type) { uint8_t *mime_data = (uint8_t *) malloc(len); if (!mime_data) { this->errorInfo.set(NULL, "malloc", errno); @@ -1037,7 +817,7 @@ Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) { mime_closure->buf = mime_data; mime_closure->len = len; - Napi::MemoryManagement::AdjustExternalMemory(env, len); + if (env) Napi::MemoryManagement::AdjustExternalMemory(*env, len); return cairo_surface_set_mime_data(_surface , mime_type @@ -1052,7 +832,7 @@ Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) { */ cairo_status_t -Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) { +ImageSurface::loadJPEGFromBuffer(uint8_t *buf, unsigned len) { BufferReader reader(buf, len); Orientation orientation = getExifOrientation(reader); @@ -1061,7 +841,7 @@ Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) { struct jpeg_decompress_struct args; struct canvas_jpeg_error_mgr err; - err.image = this; + err.surface = this; args.err = jpeg_std_error(&err); args.err->error_exit = canvas_jpeg_error_exit; args.err->output_message = canvas_jpeg_output_message; @@ -1091,14 +871,10 @@ Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) { */ cairo_status_t -Image::loadJPEG(FILE *stream) { +ImageSurface::loadJPEG(FILE *stream) { cairo_status_t status; -#if defined(_MSC_VER) - if (false) { // Force using loadJPEGFromBuffer -#else if (data_mode == DATA_IMAGE) { // Can lazily read in the JPEG. -#endif Orientation orientation = NORMAL; { StreamReader reader(stream); @@ -1110,7 +886,7 @@ Image::loadJPEG(FILE *stream) { struct jpeg_decompress_struct args; struct canvas_jpeg_error_mgr err; - err.image = this; + err.surface = this; args.err = jpeg_std_error(&err); args.err->error_exit = canvas_jpeg_error_exit; args.err->output_message = canvas_jpeg_output_message; @@ -1162,11 +938,6 @@ Image::loadJPEG(FILE *stream) { } else if (DATA_MIME == data_mode) { status = decodeJPEGBufferIntoMimeSurface(buf, len); } -#if defined(_MSC_VER) - else if (DATA_IMAGE == data_mode) { - status = loadJPEGFromBuffer(buf, len); - } -#endif else { status = CAIRO_STATUS_READ_ERROR; } @@ -1182,8 +953,8 @@ Image::loadJPEG(FILE *stream) { * Returns the Exif orientation if one exists, otherwise returns NORMAL */ -Image::Orientation -Image::getExifOrientation(Reader& jpeg) { +ImageSurface::Orientation +ImageSurface::getExifOrientation(Reader& jpeg) { static const char kJpegStartOfImage = (char)0xd8; static const char kJpegStartOfFrameBaseline = (char)0xc0; static const char kJpegStartOfFrameProgressive = (char)0xc2; @@ -1330,7 +1101,7 @@ Image::getExifOrientation(Reader& jpeg) { * Updates the dimensions of the bitmap according to the orientation */ -void Image::updateDimensionsForOrientation(Orientation orientation) { +void ImageSurface::updateDimensionsForOrientation(Orientation orientation) { switch (orientation) { case ROTATE_90_CW: case ROTATE_270_CW: @@ -1359,7 +1130,7 @@ void Image::updateDimensionsForOrientation(Orientation orientation) { */ void -Image::rotatePixels(uint8_t* pixels, int width, int height, int channels, +ImageSurface::rotatePixels(uint8_t* pixels, int width, int height, int channels, Orientation orientation) { auto swapPixel = [channels](uint8_t* pixels, int src_idx, int dst_idx) { uint8_t tmp; @@ -1457,29 +1228,26 @@ Image::rotatePixels(uint8_t* pixels, int width, int height, int channels, } } -#endif /* HAVE_JPEG */ - -#ifdef HAVE_RSVG - /* * Load SVG from buffer */ cairo_status_t -Image::loadSVGFromBuffer(uint8_t *buf, unsigned len) { - _is_svg = true; +ImageSurface::loadSVGFromBuffer(uint8_t *buf, unsigned len) { + lunasvg::GraphicsCallbacks callbacks; - if (NULL == (_rsvg = rsvg_handle_new_from_data(buf, len, nullptr))) { - return CAIRO_STATUS_READ_ERROR; - } + callbacks.setDecoderFn([](char* data, int length) { + ImageSurface bitmap(std::nullopt); + bitmap.loadFromBuffer((uint8_t*)data, length); + return bitmap.transferSurface(); + }); - double d_width; - double d_height; + svgdoc = lunasvg::Document::loadFromData((char*)buf, len, callbacks); - rsvg_handle_get_intrinsic_size_in_pixels(_rsvg, &d_width, &d_height); + if (!svgdoc) return CAIRO_STATUS_READ_ERROR; - width = naturalWidth = d_width; - height = naturalHeight = d_height; + width = naturalWidth = svgdoc->width(); + height = naturalHeight = svgdoc->height(); if (width <= 0 || height <= 0) { this->errorInfo.set("Width and height must be set on the svg element"); @@ -1490,41 +1258,18 @@ Image::loadSVGFromBuffer(uint8_t *buf, unsigned len) { } /* - * Renders the Rsvg handle to this image's surface + * Renders the lunasvg document to this image's surface */ + cairo_status_t -Image::renderSVGToSurface() { +ImageSurface::renderSVGToSurface() { cairo_status_t status; - _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); - - status = cairo_surface_status(_surface); - if (status != CAIRO_STATUS_SUCCESS) { - g_object_unref(_rsvg); - return status; - } - - cairo_t *cr = cairo_create(_surface); - status = cairo_status(cr); - if (status != CAIRO_STATUS_SUCCESS) { - g_object_unref(_rsvg); - return status; - } - - RsvgRectangle viewport = { - 0, // x - 0, // y - static_cast(width), - static_cast(height) - }; - gboolean render_ok = rsvg_handle_render_document(_rsvg, cr, &viewport, nullptr); - if (!render_ok) { - g_object_unref(_rsvg); - cairo_destroy(cr); - return CAIRO_STATUS_READ_ERROR; // or WRITE? - } - - cairo_destroy(cr); + if (_surface) cairo_surface_destroy(_surface); + lunasvg::Bitmap bitmap = svgdoc->renderToBitmap(width, height, 0); + status = cairo_surface_status(bitmap.surface()); + if (status != CAIRO_STATUS_SUCCESS) return status; + _surface = cairo_surface_reference(bitmap.surface()); _svg_last_width = width; _svg_last_height = height; @@ -1537,9 +1282,7 @@ Image::renderSVGToSurface() { */ cairo_status_t -Image::loadSVG(FILE *stream) { - _is_svg = true; - +ImageSurface::loadSVG(FILE *stream) { struct stat s; int fd = fileno(stream); @@ -1566,13 +1309,11 @@ Image::loadSVG(FILE *stream) { return result; } -#endif /* HAVE_RSVG */ - /* * Load BMP from buffer. */ -cairo_status_t Image::loadBMPFromBuffer(uint8_t *buf, unsigned len){ +cairo_status_t ImageSurface::loadBMPFromBuffer(uint8_t *buf, unsigned len){ BMPParser::Parser parser; // Reversed ARGB32 with pre-multiplied alpha @@ -1611,7 +1352,7 @@ cairo_status_t Image::loadBMPFromBuffer(uint8_t *buf, unsigned len){ * Load BMP. */ -cairo_status_t Image::loadBMP(FILE *stream){ +cairo_status_t ImageSurface::loadBMP(FILE *stream){ struct stat s; int fd = fileno(stream); @@ -1643,16 +1384,16 @@ cairo_status_t Image::loadBMP(FILE *stream){ * Return UNKNOWN, SVG, GIF, JPEG, or PNG based on the filename. */ -Image::type -Image::extension(const char *filename) { +ImageSurface::type +ImageSurface::extension(const char *filename) { size_t len = strlen(filename); filename += len; - if (len >= 5 && 0 == strcmp(".jpeg", filename - 5)) return Image::JPEG; - if (len >= 4 && 0 == strcmp(".gif", filename - 4)) return Image::GIF; - if (len >= 4 && 0 == strcmp(".jpg", filename - 4)) return Image::JPEG; - if (len >= 4 && 0 == strcmp(".png", filename - 4)) return Image::PNG; - if (len >= 4 && 0 == strcmp(".svg", filename - 4)) return Image::SVG; - return Image::UNKNOWN; + if (len >= 5 && 0 == strcmp(".jpeg", filename - 5)) return ImageSurface::JPEG; + if (len >= 4 && 0 == strcmp(".gif", filename - 4)) return ImageSurface::GIF; + if (len >= 4 && 0 == strcmp(".jpg", filename - 4)) return ImageSurface::JPEG; + if (len >= 4 && 0 == strcmp(".png", filename - 4)) return ImageSurface::PNG; + if (len >= 4 && 0 == strcmp(".svg", filename - 4)) return ImageSurface::SVG; + return ImageSurface::UNKNOWN; } /* @@ -1660,7 +1401,7 @@ Image::extension(const char *filename) { */ int -Image::isJPEG(uint8_t *data) { +ImageSurface::isJPEG(uint8_t *data) { return 0xff == data[0] && 0xd8 == data[1]; } @@ -1669,7 +1410,7 @@ Image::isJPEG(uint8_t *data) { */ int -Image::isGIF(uint8_t *data) { +ImageSurface::isGIF(uint8_t *data) { return 'G' == data[0] && 'I' == data[1] && 'F' == data[2]; } @@ -1678,7 +1419,7 @@ Image::isGIF(uint8_t *data) { */ int -Image::isPNG(uint8_t *data) { +ImageSurface::isPNG(uint8_t *data) { return 'P' == data[1] && 'N' == data[2] && 'G' == data[3]; } @@ -1686,7 +1427,7 @@ Image::isPNG(uint8_t *data) { * Skip "(); + Napi::HandleScope scope(env); + + Napi::Function ctor = DefineClass(env, "Image", { + InstanceAccessor<&Image::GetComplete>("complete", napi_default_jsproperty), + InstanceAccessor<&Image::GetWidth, &Image::SetWidth>("width", napi_default_jsproperty), + InstanceAccessor<&Image::GetHeight, &Image::SetHeight>("height", napi_default_jsproperty), + InstanceAccessor<&Image::GetNaturalWidth>("naturalWidth", napi_default_jsproperty), + InstanceAccessor<&Image::GetNaturalHeight>("naturalHeight", napi_default_jsproperty), + InstanceAccessor<&Image::GetDataMode, &Image::SetDataMode>("dataMode", napi_default_jsproperty), + StaticValue("MODE_IMAGE", Napi::Number::New(env, ImageSurface::DATA_IMAGE), napi_default_jsproperty), + StaticValue("MODE_MIME", Napi::Number::New(env, ImageSurface::DATA_MIME), napi_default_jsproperty) + }); + + // Used internally in lib/image.js + exports.Set("GetSource", Napi::Function::New(env, &GetSource)); + exports.Set("SetSource", Napi::Function::New(env, &SetSource)); + + data->ImageCtor = Napi::Persistent(ctor); + exports.Set("Image", ctor); +} + +/* + * Initialize a new Image. + */ + +Image::Image(const Napi::CallbackInfo& info) : ObjectWrap(info), env(info.Env()) , surface(env) { + info.This().ToObject().Unwrap().Set("onload", env.Null()); + info.This().ToObject().Unwrap().Set("onerror", env.Null()); +} + +/* + * Get complete boolean. + */ + +Napi::Value +Image::GetComplete(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(env, true); +} + +/* + * Get dataMode. + */ + +Napi::Value +Image::GetDataMode(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, surface.data_mode); +} + +/* + * Set dataMode. + */ + +void +Image::SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsNumber()) { + int mode = value.As().Uint32Value(); + surface.data_mode = (ImageSurface::data_mode_t) mode; + } +} + +/* + * Get natural width + */ + +Napi::Value +Image::GetNaturalWidth(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, surface.naturalWidth); +} + +/* + * Get width. + */ + +Napi::Value +Image::GetWidth(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, surface.width); +} + +/* + * Set width. + */ + +void +Image::SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsNumber()) { + surface.width = value.As().Uint32Value(); + } +} + +/* + * Get natural height + */ + +Napi::Value +Image::GetNaturalHeight(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, surface.naturalHeight); +} + +/* + * Get height. + */ + +Napi::Value +Image::GetHeight(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, surface.height); +} +/* + * Set height. + */ + +void +Image::SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsNumber()) { + surface.height = value.As().Uint32Value(); + } +} + +/* + * Get src path. + */ + +Napi::Value +Image::GetSource(const Napi::CallbackInfo& info){ + Napi::Env env = info.Env(); + Image *img = Image::Unwrap(info.This().As()); + return Napi::String::New(env, img->surface.filename ? img->surface.filename : ""); +} + +/* + * Set src path. + */ + +void +Image::SetSource(const Napi::CallbackInfo& info){ + Napi::Env env = info.Env(); + Napi::Object This = info.This().As(); + Image *img = Image::Unwrap(This); + cairo_status_t status = CAIRO_STATUS_READ_ERROR; + + Napi::Value value = info[0]; + + // url string + if (value.IsString()) { + std::string src = value.As().Utf8Value(); + status = img->surface.load(src); + // Buffer + } else if (value.IsBuffer()) { + uint8_t *buf = value.As>().Data(); + unsigned len = value.As>().Length(); + status = img->surface.loadFromBuffer(buf, len); + } + + if (status) { + Napi::Value onerrorFn; + if (This.Get("onerror").UnwrapTo(&onerrorFn) && onerrorFn.IsFunction()) { + Napi::Error arg; + if (img->surface.errorInfo.empty()) { + arg = Napi::Error::New(env, Napi::String::New(env, cairo_status_to_string(status))); + } else { + arg = img->surface.errorInfo.toError(env); + } + onerrorFn.As().Call({ arg.Value() }); + } + } else { + img->surface.loaded(); + Napi::Value onloadFn; + if (This.Get("onload").UnwrapTo(&onloadFn) && onloadFn.IsFunction()) { + onloadFn.As().Call({}); + } + } +} + diff --git a/src/Image.h b/src/Image.h index 56bcc989b..6e07a6e0d 100644 --- a/src/Image.h +++ b/src/Image.h @@ -7,51 +7,27 @@ #include #include #include // node < 7 uses libstdc++ on macOS which lacks complete c++11 +#include -#ifdef HAVE_JPEG #include #include -#endif -#ifdef HAVE_GIF #include +#define GIF_CLOSE_FILE(gif) DGifCloseFile(gif, NULL) - #if GIFLIB_MAJOR > 5 || GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1 - #define GIF_CLOSE_FILE(gif) DGifCloseFile(gif, NULL) - #else - #define GIF_CLOSE_FILE(gif) DGifCloseFile(gif) - #endif -#endif - -#ifdef HAVE_RSVG -#include - // librsvg <= 2.36.1, identified by undefined macro, needs an extra include - #ifndef LIBRSVG_CHECK_VERSION - #include - #endif -#endif +#include using JPEGDecodeL = std::function; -class Image : public Napi::ObjectWrap { +class ImageSurface { public: + // Only contains a value when the ImageSurface backs a js Image. Empty for + // ImageSurfaces supporting lunasvg (to render images embedded in SVGs). + std::optional env; char *filename; int width, height; int naturalWidth, naturalHeight; - Napi::Env env; - static void Initialize(Napi::Env& env, Napi::Object& target); - Image(const Napi::CallbackInfo& info); - Napi::Value GetComplete(const Napi::CallbackInfo& info); - Napi::Value GetWidth(const Napi::CallbackInfo& info); - Napi::Value GetHeight(const Napi::CallbackInfo& info); - Napi::Value GetNaturalWidth(const Napi::CallbackInfo& info); - Napi::Value GetNaturalHeight(const Napi::CallbackInfo& info); - Napi::Value GetDataMode(const Napi::CallbackInfo& info); - void SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value); - void SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value); - void SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value); - static Napi::Value GetSource(const Napi::CallbackInfo& info); - static void SetSource(const Napi::CallbackInfo& info); + ImageSurface(std::optional env); inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); } inline int stride(){ return cairo_image_surface_get_stride(_surface); } static int isPNG(uint8_t *data); @@ -60,23 +36,18 @@ class Image : public Napi::ObjectWrap { static int isSVG(uint8_t *data, unsigned len); static int isBMP(uint8_t *data, unsigned len); static cairo_status_t readPNG(void *closure, unsigned char *data, unsigned len); - inline int isComplete(){ return COMPLETE == state; } cairo_surface_t *surface(); cairo_status_t loadSurface(); cairo_status_t loadFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadPNGFromBuffer(uint8_t *buf); cairo_status_t loadPNG(); void clearData(); -#ifdef HAVE_RSVG + cairo_surface_t* transferSurface(); cairo_status_t loadSVGFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadSVG(FILE *stream); cairo_status_t renderSVGToSurface(); -#endif -#ifdef HAVE_GIF cairo_status_t loadGIFFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadGIF(FILE *stream); -#endif -#ifdef HAVE_JPEG enum Orientation { NORMAL, MIRROR_HORIZ, @@ -103,13 +74,12 @@ class Image : public Napi::ObjectWrap { Orientation getExifOrientation(Reader& jpeg); void updateDimensionsForOrientation(Orientation orientation); void rotatePixels(uint8_t* pixels, int width, int height, int channels, Orientation orientation); -#endif cairo_status_t loadBMPFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadBMP(FILE *stream); CanvasError errorInfo; void loaded(); - cairo_status_t load(); - ~Image(); + cairo_status_t load(std::string& filename); + ~ImageSurface(); enum { DEFAULT @@ -136,10 +106,27 @@ class Image : public Napi::ObjectWrap { cairo_surface_t *_surface; uint8_t *_data = nullptr; int _data_len; -#ifdef HAVE_RSVG - RsvgHandle *_rsvg; - bool _is_svg; + std::unique_ptr svgdoc; int _svg_last_width; int _svg_last_height; -#endif +}; + +class Image : public Napi::ObjectWrap { + public: + Napi::Env env; + static void Initialize(Napi::Env& env, Napi::Object& target); + Image(const Napi::CallbackInfo& info); + Napi::Value GetComplete(const Napi::CallbackInfo& info); + Napi::Value GetWidth(const Napi::CallbackInfo& info); + Napi::Value GetHeight(const Napi::CallbackInfo& info); + Napi::Value GetNaturalWidth(const Napi::CallbackInfo& info); + Napi::Value GetNaturalHeight(const Napi::CallbackInfo& info); + Napi::Value GetDataMode(const Napi::CallbackInfo& info); + void SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value); + static Napi::Value GetSource(const Napi::CallbackInfo& info); + static void SetSource(const Napi::CallbackInfo& info); + inline int isComplete(){ return ImageSurface::COMPLETE == surface.state; } + ImageSurface surface; }; diff --git a/src/InstanceData.h b/src/InstanceData.h index 983216ace..b8844ffad 100644 --- a/src/InstanceData.h +++ b/src/InstanceData.h @@ -1,4 +1,19 @@ +#pragma once + #include +#include +#include "FontFaceSet.h" +#ifdef _WIN32 +#include "FontManagerWindows.h" +using PlatformFontManager = FontManagerWindows; +#elif __APPLE__ +#include "FontManagerMacos.h" +using PlatformFontManager = FontManagerMacos; +#else +// Linux, the BSDs, and other Unixes that use FontConfig. +#include "FontManagerLinux.h" +using PlatformFontManager = FontManagerLinux; +#endif struct InstanceData { Napi::FunctionReference CanvasCtor; @@ -9,4 +24,17 @@ struct InstanceData { Napi::FunctionReference Context2dCtor; Napi::FunctionReference ImageDataCtor; Napi::FunctionReference CanvasPatternCtor; + Napi::FunctionReference FontFaceCtor; + Napi::ObjectReference jsFontSet; + FontFaceSet* cppFontSet; + FT_Library ft; + PlatformFontManager fontManager; + + InstanceData() { + FT_Init_FreeType(&ft); + } + + ~InstanceData() { + FT_Done_FreeType(ft); + } }; diff --git a/src/PNG.h b/src/PNG.h index 30b88f85f..094fb12cf 100644 --- a/src/PNG.h +++ b/src/PNG.h @@ -8,14 +8,6 @@ #include #include -#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) -#define likely(expr) (__builtin_expect (!!(expr), 1)) -#define unlikely(expr) (__builtin_expect (!!(expr), 0)) -#else -#define likely(expr) (expr) -#define unlikely(expr) (expr) -#endif - static void canvas_png_flush(png_structp png_ptr) { /* Do nothing; fflush() is said to be just a waste of energy. */ (void) png_ptr; /* Stifle compiler warning */ @@ -87,11 +79,9 @@ struct canvas_png_write_closure_t { PngClosure* closure; }; -#ifdef PNG_SETJMP_SUPPORTED bool setjmp_wrapper(png_structp png) { return setjmp(png_jmpbuf(png)); } -#endif static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr write_func, canvas_png_write_closure_t *closure) { unsigned int i; @@ -119,7 +109,7 @@ static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr writ } rows = (png_bytep *) malloc(height * sizeof (png_byte*)); - if (unlikely(rows == NULL)) { + if (rows == NULL) [[unlikely]] { status = CAIRO_STATUS_NO_MEMORY; return status; } @@ -129,20 +119,16 @@ static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr writ rows[i] = (png_byte *) data + i * stride; } -#ifdef PNG_USER_MEM_SUPPORTED png = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, NULL, NULL); -#else - png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); -#endif - if (unlikely(png == NULL)) { + if (png == NULL) [[unlikely]] { status = CAIRO_STATUS_NO_MEMORY; free(rows); return status; } info = png_create_info_struct (png); - if (unlikely(info == NULL)) { + if (info == NULL) [[unlikely]] { status = CAIRO_STATUS_NO_MEMORY; png_destroy_write_struct(&png, &info); free(rows); @@ -150,13 +136,11 @@ static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr writ } -#ifdef PNG_SETJMP_SUPPORTED if (setjmp_wrapper(png)) { png_destroy_write_struct(&png, &info); free(rows); return status; } -#endif png_set_write_fn(png, closure, write_func, canvas_png_flush); png_set_compression_level(png, closure->closure->compressionLevel); @@ -173,12 +157,10 @@ static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr writ bpc = 8; png_color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; -#ifdef CAIRO_FORMAT_RGB30 case CAIRO_FORMAT_RGB30: bpc = 10; png_color_type = PNG_COLOR_TYPE_RGB; break; -#endif case CAIRO_FORMAT_RGB24: bpc = 8; png_color_type = PNG_COLOR_TYPE_RGB; @@ -269,7 +251,7 @@ static void canvas_stream_write_func(png_structp png, png_bytep data, png_size_t png_closure = (struct canvas_png_write_closure_t *) png_get_io_ptr(png); status = png_closure->write_func(png_closure->closure, data, size); - if (unlikely(status)) { + if (status) [[unlikely]] { cairo_status_t *error = (cairo_status_t *) png_get_error_ptr(png); if (*error == CAIRO_STATUS_SUCCESS) { *error = status; diff --git a/src/Util.h b/src/Util.h index 0e6d1d89c..5d57135a4 100644 --- a/src/Util.h +++ b/src/Util.h @@ -2,6 +2,15 @@ #include +#define LOG_LEVEL 0 + +#if LOG_LEVEL > 0 +#include +#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else +#define LOG(fmt, ...) +#endif + inline bool streq_casein(std::string& str1, std::string& str2) { return str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](char& c1, char& c2) { return c1 == c2 || std::toupper(c1) == std::toupper(c2); diff --git a/src/bmp/BMPParser.h b/src/bmp/BMPParser.h index c35f51a86..0f503a7f6 100644 --- a/src/bmp/BMPParser.h +++ b/src/bmp/BMPParser.h @@ -1,60 +1,50 @@ -#pragma once - -#ifdef ERROR -#define ERROR_ ERROR -#undef ERROR -#endif - -#include // node < 7 uses libstdc++ on macOS which lacks complete c++11 -#include - -namespace BMPParser{ - enum Status{ - EMPTY, - OK, - ERROR, - }; - - class Parser{ - public: - Parser()=default; - ~Parser(); - void parse(uint8_t *buf, int bufSize, uint8_t *format=nullptr); - void clearImgd(); - int32_t getWidth() const; - int32_t getHeight() const; - uint8_t *getImgd() const; - Status getStatus() const; - std::string getErrMsg() const; - - private: - Status status = Status::EMPTY; - uint8_t *data = nullptr; - uint8_t *ptr = nullptr; - int len = 0; - int32_t w = 0; - int32_t h = 0; - uint8_t *imgd = nullptr; - std::string err = ""; - std::string op = ""; - - template inline T get(); - template inline T get(uint8_t* pointer); - std::string getStr(int len, bool reverse=false); - inline void skip(int len); - void calcMaskShift(uint32_t& shift, uint32_t& mask, double& multp); - - void setOp(std::string val); - std::string getOp() const; - - void setErrUnsupported(std::string msg); - void setErrUnknown(std::string msg); - void setErr(std::string msg); - std::string getErr() const; - }; -} - -#ifdef ERROR_ -#define ERROR ERROR_ -#undef ERROR_ -#endif +#pragma once + +#include // node < 7 uses libstdc++ on macOS which lacks complete c++11 +#include + +namespace BMPParser{ + enum Status{ + EMPTY, + OK, + ERROR, + }; + + class Parser{ + public: + Parser()=default; + ~Parser(); + void parse(uint8_t *buf, int bufSize, uint8_t *format=nullptr); + void clearImgd(); + int32_t getWidth() const; + int32_t getHeight() const; + uint8_t *getImgd() const; + Status getStatus() const; + std::string getErrMsg() const; + + private: + Status status = Status::EMPTY; + uint8_t *data = nullptr; + uint8_t *ptr = nullptr; + int len = 0; + int32_t w = 0; + int32_t h = 0; + uint8_t *imgd = nullptr; + std::string err = ""; + std::string op = ""; + + template inline T get(); + template inline T get(uint8_t* pointer); + std::string getStr(int len, bool reverse=false); + inline void skip(int len); + void calcMaskShift(uint32_t& shift, uint32_t& mask, double& multp); + + void setOp(std::string val); + std::string getOp() const; + + void setErrUnsupported(std::string msg); + void setErrUnknown(std::string msg); + void setErr(std::string msg); + std::string getErr() const; + }; +} diff --git a/src/closure.cc b/src/closure.cc index 3290db2e5..231da899c 100644 --- a/src/closure.cc +++ b/src/closure.cc @@ -1,7 +1,6 @@ #include "closure.h" #include "Canvas.h" -#ifdef HAVE_JPEG void JpegClosure::init_destination(j_compress_ptr cinfo) { JpegClosure* closure = (JpegClosure*)cinfo->client_data; closure->vec.resize(PAGE_SIZE); @@ -23,7 +22,6 @@ void JpegClosure::term_destination(j_compress_ptr cinfo) { size_t finalSize = closure->vec.size() - closure->jpeg_dest_mgr->free_in_buffer; closure->vec.resize(finalSize); } -#endif void EncodingWorker::Init(void (*work_fn)(Closure*), Closure* closure) { diff --git a/src/closure.h b/src/closure.h index 39aaf57bf..1e17d9a57 100644 --- a/src/closure.h +++ b/src/closure.h @@ -7,20 +7,16 @@ class Canvas; #include "Canvas.h" -#ifdef HAVE_JPEG #include #include #include -#endif #include #include #include // node < 7 uses libstdc++ on macOS which lacks complete c++11 #include -#ifndef PAGE_SIZE - #define PAGE_SIZE 4096 -#endif +#define PAGE_SIZE 4096 /* * Image encoding closures. @@ -61,7 +57,6 @@ struct PngClosure : Closure { PngClosure(Canvas* canvas) : Closure(canvas) {}; }; -#ifdef HAVE_JPEG struct JpegClosure : Closure { uint32_t quality = 75; uint32_t chromaSubsampling = 2; @@ -83,7 +78,6 @@ struct JpegClosure : Closure { delete jpeg_dest_mgr; } }; -#endif class EncodingWorker : public Napi::AsyncWorker { public: diff --git a/src/color.cc b/src/color.cc index f82629460..6f77c1b45 100644 --- a/src/color.cc +++ b/src/color.cc @@ -10,11 +10,6 @@ #include #include -// Compatibility with Visual Studio versions prior to VS2015 -#if defined(_MSC_VER) && _MSC_VER < 1900 -#define snprintf _snprintf -#endif - /* * Parse integer value */ diff --git a/src/dll_visibility.h b/src/dll_visibility.h deleted file mode 100644 index 7a1f98450..000000000 --- a/src/dll_visibility.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef DLL_PUBLIC - -#if defined _WIN32 - #ifdef __GNUC__ - #define DLL_PUBLIC __attribute__ ((dllexport)) - #else - #define DLL_PUBLIC __declspec(dllexport) - #endif - #define DLL_LOCAL -#else - #if __GNUC__ >= 4 - #define DLL_PUBLIC __attribute__ ((visibility ("default"))) - #define DLL_LOCAL __attribute__ ((visibility ("hidden"))) - #else - #define DLL_PUBLIC - #define DLL_LOCAL - #endif -#endif - -#endif diff --git a/src/init.cc b/src/init.cc index fa3ca5166..64e5b93ba 100644 --- a/src/init.cc +++ b/src/init.cc @@ -1,18 +1,11 @@ // Copyright (c) 2010 LearnBoost #include -#include #include -#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0) -// CAIRO_FORMAT_RGB16_565: undeprecated in v1.10.0 -// CAIRO_STATUS_INVALID_SIZE: v1.10.0 -// CAIRO_FORMAT_INVALID: v1.10.0 -// Lots of the compositing operators: v1.10.0 -// JPEG MIME tracking: v1.10.0 -// Note: CAIRO_FORMAT_RGB30 is v1.12.0 and still optional -#error("cairo v1.10.0 or later is required") -#endif +#include +#include +#include #include "Canvas.h" #include "CanvasGradient.h" @@ -21,9 +14,8 @@ #include "Image.h" #include "ImageData.h" #include "InstanceData.h" - -#include -#include FT_FREETYPE_H +#include "FontFace.h" +#include "FontFaceSet.h" /* * Save some external modules as private references. @@ -41,13 +33,10 @@ setParseFont(const Napi::CallbackInfo& info) { data->parseFont = Napi::Persistent(info[0].As()); } -// Compatibility with Visual Studio versions prior to VS2015 -#if defined(_MSC_VER) && _MSC_VER < 1900 -#define snprintf _snprintf -#endif - Napi::Object init(Napi::Env env, Napi::Object exports) { - env.SetInstanceData(new InstanceData()); + InstanceData* data = new InstanceData(); + env.SetInstanceData(data); + data->fontManager.readSystemFonts(); Canvas::Initialize(env, exports); Image::Initialize(env, exports); @@ -55,28 +44,16 @@ Napi::Object init(Napi::Env env, Napi::Object exports) { Context2d::Initialize(env, exports); Gradient::Initialize(env, exports); Pattern::Initialize(env, exports); + FontFace::Initialize(env, exports); + FontFaceSet::Initialize(env, exports); exports.Set("setDOMMatrix", Napi::Function::New(env, &setDOMMatrix)); exports.Set("setParseFont", Napi::Function::New(env, &setParseFont)); exports.Set("cairoVersion", Napi::String::New(env, cairo_version_string())); -#ifdef HAVE_JPEG -#ifndef JPEG_LIB_VERSION_MAJOR -#ifdef JPEG_LIB_VERSION #define JPEG_LIB_VERSION_MAJOR (JPEG_LIB_VERSION / 10) -#else -#define JPEG_LIB_VERSION_MAJOR 0 -#endif -#endif - -#ifndef JPEG_LIB_VERSION_MINOR -#ifdef JPEG_LIB_VERSION #define JPEG_LIB_VERSION_MINOR (JPEG_LIB_VERSION % 10) -#else -#define JPEG_LIB_VERSION_MINOR 0 -#endif -#endif char jpeg_version[10]; static bool minor_gt_0 = JPEG_LIB_VERSION_MINOR > 0; @@ -86,23 +63,11 @@ Napi::Object init(Napi::Env env, Napi::Object exports) { snprintf(jpeg_version, 10, "%d", JPEG_LIB_VERSION_MAJOR); } exports.Set("jpegVersion", Napi::String::New(env, jpeg_version)); -#endif -#ifdef HAVE_GIF -#ifndef GIF_LIB_VERSION char gif_version[10]; snprintf(gif_version, 10, "%d.%d.%d", GIFLIB_MAJOR, GIFLIB_MINOR, GIFLIB_RELEASE); exports.Set("gifVersion", Napi::String::New(env, gif_version)); -#else - exports.Set("gifVersion", Napi::String::New(env, GIF_LIB_VERSION)); -#endif -#endif - -#ifdef HAVE_RSVG - exports.Set("rsvgVersion", Napi::String::New(env, LIBRSVG_VERSION)); -#endif - - exports.Set("pangoVersion", Napi::String::New(env, PANGO_VERSION_STRING)); + exports.Set("lunasvgVersion", Napi::String::New(env, LUNASVG_VERSION_STRING)); char freetype_version[10]; snprintf(freetype_version, 10, "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); diff --git a/src/itemize.cc b/src/itemize.cc new file mode 100644 index 000000000..a23a2b8d5 --- /dev/null +++ b/src/itemize.cc @@ -0,0 +1,225 @@ +#include +#include +#include +#include "itemize.h" + +void +bidi_iterator_next( + BidiIteratorState& state, + char16_t* textBuffer, + size_t textLength +) { + if (state.done) return; + + state.level = state.levels[state.offset]; + + bool should_break = false; + while (state.offset < textLength && !should_break) { + while (state.offset < textLength) { + if (state.levels[state.offset] != state.level) { + should_break = true; + break; + } + + state.offset += 1; + } + } + + if (state.offset == textLength) state.done = true; +} + +static const uint32_t paired_chars[] = { + 0x0028, 0x0029, /* ascii paired punctuation */ + 0x003c, 0x003e, + 0x005b, 0x005d, + 0x007b, 0x007d, + 0x00ab, 0x00bb, /* guillemets */ + 0x0f3a, 0x0f3b, /* tibetan */ + 0x0f3c, 0x0f3d, + 0x169b, 0x169c, /* ogham */ + 0x2018, 0x2019, /* general punctuation */ + 0x201c, 0x201d, + 0x2039, 0x203a, + 0x2045, 0x2046, + 0x207d, 0x207e, + 0x208d, 0x208e, + 0x27e6, 0x27e7, /* math */ + 0x27e8, 0x27e9, + 0x27ea, 0x27eb, + 0x27ec, 0x27ed, + 0x27ee, 0x27ef, + 0x2983, 0x2984, + 0x2985, 0x2986, + 0x2987, 0x2988, + 0x2989, 0x298a, + 0x298b, 0x298c, + 0x298d, 0x298e, + 0x298f, 0x2990, + 0x2991, 0x2992, + 0x2993, 0x2994, + 0x2995, 0x2996, + 0x2997, 0x2998, + 0x29fc, 0x29fd, + 0x2e02, 0x2e03, + 0x2e04, 0x2e05, + 0x2e09, 0x2e0a, + 0x2e0c, 0x2e0d, + 0x2e1c, 0x2e1d, + 0x2e20, 0x2e21, + 0x2e22, 0x2e23, + 0x2e24, 0x2e25, + 0x2e26, 0x2e27, + 0x2e28, 0x2e29, + 0x3008, 0x3009, /* chinese paired punctuation */ + 0x300a, 0x300b, + 0x300c, 0x300d, + 0x300e, 0x300f, + 0x3010, 0x3011, + 0x3014, 0x3015, + 0x3016, 0x3017, + 0x3018, 0x3019, + 0x301a, 0x301b, + 0xfe59, 0xfe5a, + 0xfe5b, 0xfe5c, + 0xfe5d, 0xfe5e, + 0xff08, 0xff09, + 0xff3b, 0xff3d, + 0xff5b, 0xff5d, + 0xff5f, 0xff60, + 0xff62, 0xff63 +}; + +static const size_t paired_chars_count = sizeof(paired_chars) / sizeof(paired_chars[0]); + +static int +get_pair_index(uint32_t ch) { + int lower = 0; + int upper = paired_chars_count - 1; + + while (lower <= upper) { + int mid = (lower + upper) / 2; + + if (ch < paired_chars[mid]) { + upper = mid - 1; + } else if (ch > paired_chars[mid]) { + lower = mid + 1; + } else { + return mid; + } + } + + return -1; +} + +void +script_iterator_next( + ScriptIteratorState& state, + char16_t* textBuffer, + size_t textLength +) { + if (state.done) return; + + state.script = SCRIPT_COMMON; + + while (state.offset < textLength) { + uint32_t code = textBuffer[state.offset]; + int jump = 1; + + // Handle surrogate pairs + if (state.offset + 1 < textLength) { + uint32_t next = textBuffer[state.offset + 1]; + if ((0xd800 <= code && code <= 0xdbff) && (0xdc00 <= next && next <= 0xdfff)) { + jump = 2; + code = ((code - 0xd800) * 0x400) + (next - 0xdc00) + 0x10000; + } + } + + script_t script = get_script(code); + int pair_index = script != SCRIPT_COMMON ? -1 : get_pair_index(code); + + // Paired character handling: + // if it's an open character, push it onto the stack + // if it's a close character, find the matching open on the stack, and use + // that script code. Any non-matching open characters above it on the stack + // will be popped. + if (pair_index >= 0) { + if ((pair_index & 1) == 0) { + // Open character + state.parens.push_back({pair_index, state.script}); + } else if (!state.parens.empty()) { + // Close character + int pi = pair_index & ~1; + + while (!state.parens.empty() && state.parens.back().index != pi) { + state.parens.pop_back(); + } + + if (static_cast(state.parens.size()) - 1 < state.start_paren) { + state.start_paren = static_cast(state.parens.size()) - 1; + } + + if (!state.parens.empty()) { + script = state.parens.back().script; + } + } + } + + bool running_is_real = state.script != SCRIPT_COMMON && state.script != SCRIPT_INHERITED; + bool is_real = script != SCRIPT_COMMON && script != SCRIPT_INHERITED; + bool is_same = !running_is_real || !is_real || script == state.script; + + if (is_same) { + if (!running_is_real && is_real) { + state.script = script; + + // Now that we have a final script code, fix any open characters we + // pushed before we knew the real script code. + while (state.start_paren + 1 < static_cast(state.parens.size())) { + state.parens[++state.start_paren].script = script; + } + + if (pair_index >= 0 && (pair_index & 1) && !state.parens.empty()) { + state.parens.pop_back(); + + if (static_cast(state.parens.size()) - 1 < state.start_paren) { + state.start_paren = static_cast(state.parens.size()) - 1; + } + } + } + + state.offset += jump; + } else { + state.start_paren = static_cast(state.parens.size()) - 1; + break; + } + } + + if (state.offset >= textLength) { + state.done = true; + } +} + +void +itemizeNext(ItemizeState& state) { + if (state.done) return; + + if (state.bidi_state.offset == state.offset) { + bidi_iterator_next(state.bidi_state, state.textBuffer, state.textLength); + } + + if (state.script_state.offset == state.offset) { + script_iterator_next(state.script_state, state.textBuffer, state.textLength); + } + + state.offset = std::min( + std::min( + state.bidi_state.offset, + state.script_state.offset + ), + state.textLength + ); + + if (state.bidi_state.done && state.script_state.done) { + state.done = true; + } +} diff --git a/src/itemize.h b/src/itemize.h new file mode 100644 index 000000000..d114a3054 --- /dev/null +++ b/src/itemize.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include "unicode.h" + +struct ParenInfo { + int index; + script_t script; +}; + +struct ScriptIteratorState { + // Output + size_t offset = 0; + script_t script = SCRIPT_COMMON; + bool done = false; + + // Private state + std::vector parens; + int start_paren = -1; +}; + +struct BidiIteratorState { + BidiIteratorState( + char16_t* textBuffer, + size_t textLength, + uint8_t initialLevel + ) { + SBCodepointSequence codepointSequence = { + SBStringEncodingUTF16, + textBuffer, + textLength + }; + algorithm = SBAlgorithmCreate(&codepointSequence); + paragraph = SBAlgorithmCreateParagraph( + algorithm, + offset, + textLength, + initialLevel + ); + levels = SBParagraphGetLevelsPtr(paragraph); + } + + ~BidiIteratorState() { + if (paragraph != nullptr) SBParagraphRelease(paragraph); + if (algorithm != nullptr) SBAlgorithmRelease(algorithm); + } + + // Output + size_t offset = 0; + uint8_t level = 0; + bool done = false; + + // Private state + SBAlgorithmRef algorithm = nullptr; + SBParagraphRef paragraph = nullptr; + const SBLevel* levels = nullptr; +}; + +struct ItemizeState { + ItemizeState( + char16_t* textBuffer, + size_t textLength, + uint8_t initialLevel + ) : textBuffer(textBuffer) + , textLength(textLength) + , bidi_state(textBuffer, textLength, initialLevel) {} + + // Output + size_t offset = 0; + bool done = false; + + // Private state + char16_t* textBuffer; + size_t textLength; + BidiIteratorState bidi_state; + ScriptIteratorState script_state; +}; + +void itemizeNext(ItemizeState& state); diff --git a/src/register_font.cc b/src/register_font.cc deleted file mode 100644 index ae2ece584..000000000 --- a/src/register_font.cc +++ /dev/null @@ -1,352 +0,0 @@ -#include "register_font.h" - -#include -#include -#include - -#ifdef __APPLE__ -#include -#elif defined(_WIN32) -#include -#include -#else -#include -#endif - -#include -#include FT_FREETYPE_H -#include FT_TRUETYPE_TABLES_H -#include FT_SFNT_NAMES_H -#include FT_TRUETYPE_IDS_H -#ifndef FT_SFNT_OS2 -#define FT_SFNT_OS2 ft_sfnt_os2 -#endif - -// OSX seems to read the strings in MacRoman encoding and ignore Unicode entries. -// You can verify this by opening a TTF with both Unicode and Macroman on OSX. -// It uses the MacRoman name, while Fontconfig and Windows use Unicode -#ifdef __APPLE__ -#define PREFERRED_PLATFORM_ID TT_PLATFORM_MACINTOSH -#define PREFERRED_ENCODING_ID TT_MAC_ID_ROMAN -#else -#define PREFERRED_PLATFORM_ID TT_PLATFORM_MICROSOFT -#define PREFERRED_ENCODING_ID TT_MS_ID_UNICODE_CS -#endif - -#define IS_PREFERRED_ENC(X) \ - X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID - -#define GET_NAME_RANK(X) \ - (IS_PREFERRED_ENC(X) ? 1 : 0) + (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0) - -/* - * Return a UTF-8 encoded string given a TrueType name buf+len - * and its platform and encoding - */ - -char * -to_utf8(FT_Byte* buf, FT_UInt len, FT_UShort pid, FT_UShort eid) { - size_t ret_len = len * 4; // max chars in a utf8 string - char *ret = (char*)malloc(ret_len + 1); // utf8 string + null - - if (!ret) return NULL; - - // In my testing of hundreds of fonts from the Google Font repo, the two types - // of fonts are TT_PLATFORM_MICROSOFT with TT_MS_ID_UNICODE_CS encoding, or - // TT_PLATFORM_MACINTOSH with TT_MAC_ID_ROMAN encoding. Usually both, never neither - - char const *fromcode; - - if (pid == TT_PLATFORM_MACINTOSH && eid == TT_MAC_ID_ROMAN) { - fromcode = "MAC"; - } else if (pid == TT_PLATFORM_MICROSOFT && eid == TT_MS_ID_UNICODE_CS) { - fromcode = "UTF-16BE"; - } else { - free(ret); - return NULL; - } - - GIConv cd = g_iconv_open("UTF-8", fromcode); - - if (cd == (GIConv)-1) { - free(ret); - return NULL; - } - - size_t inbytesleft = len; - size_t outbytesleft = ret_len; - - size_t n_converted = g_iconv(cd, (char**)&buf, &inbytesleft, &ret, &outbytesleft); - - ret -= ret_len - outbytesleft; // rewind the pointers to their - buf -= len - inbytesleft; // original starting positions - - if (n_converted == (size_t)-1) { - free(ret); - return NULL; - } else { - ret[ret_len - outbytesleft] = '\0'; - return ret; - } -} - -/* - * Find a family name in the face's name table, preferring the one the - * system, fall back to the other - */ - -char * -get_family_name(FT_Face face) { - FT_SfntName name; - - int best_rank = -1; - char* best_buf = NULL; - - for (unsigned i = 0; i < FT_Get_Sfnt_Name_Count(face); ++i) { - FT_Get_Sfnt_Name(face, i, &name); - - if (name.name_id == TT_NAME_ID_FONT_FAMILY || name.name_id == TT_NAME_ID_PREFERRED_FAMILY) { - char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id); - - if (buf) { - int rank = GET_NAME_RANK(name); - if (rank > best_rank) { - best_rank = rank; - if (best_buf) free(best_buf); - best_buf = buf; - } else { - free(buf); - } - } - } - } - - return best_buf; -} - -PangoWeight -get_pango_weight(FT_UShort weight) { - switch (weight) { - case 100: return PANGO_WEIGHT_THIN; - case 200: return PANGO_WEIGHT_ULTRALIGHT; - case 300: return PANGO_WEIGHT_LIGHT; - #if PANGO_VERSION >= PANGO_VERSION_ENCODE(1, 36, 7) - case 350: return PANGO_WEIGHT_SEMILIGHT; - #endif - case 380: return PANGO_WEIGHT_BOOK; - case 400: return PANGO_WEIGHT_NORMAL; - case 500: return PANGO_WEIGHT_MEDIUM; - case 600: return PANGO_WEIGHT_SEMIBOLD; - case 700: return PANGO_WEIGHT_BOLD; - case 800: return PANGO_WEIGHT_ULTRABOLD; - case 900: return PANGO_WEIGHT_HEAVY; - case 1000: return PANGO_WEIGHT_ULTRAHEAVY; - default: return PANGO_WEIGHT_NORMAL; - } -} - -PangoStretch -get_pango_stretch(FT_UShort width) { - switch (width) { - case 1: return PANGO_STRETCH_ULTRA_CONDENSED; - case 2: return PANGO_STRETCH_EXTRA_CONDENSED; - case 3: return PANGO_STRETCH_CONDENSED; - case 4: return PANGO_STRETCH_SEMI_CONDENSED; - case 5: return PANGO_STRETCH_NORMAL; - case 6: return PANGO_STRETCH_SEMI_EXPANDED; - case 7: return PANGO_STRETCH_EXPANDED; - case 8: return PANGO_STRETCH_EXTRA_EXPANDED; - case 9: return PANGO_STRETCH_ULTRA_EXPANDED; - default: return PANGO_STRETCH_NORMAL; - } -} - -PangoStyle -get_pango_style(FT_Long flags) { - if (flags & FT_STYLE_FLAG_ITALIC) { - return PANGO_STYLE_ITALIC; - } else { - return PANGO_STYLE_NORMAL; - } -} - -#ifdef _WIN32 -std::unique_ptr -u8ToWide(const char* str) { - int iBufferSize = MultiByteToWideChar(CP_UTF8, 0, str, -1, (wchar_t*)NULL, 0); - if(!iBufferSize){ - return nullptr; - } - std::unique_ptr wpBufWString = std::unique_ptr{ new wchar_t[static_cast(iBufferSize)] }; - if(!MultiByteToWideChar(CP_UTF8, 0, str, -1, wpBufWString.get(), iBufferSize)){ - return nullptr; - } - return wpBufWString; -} - -static unsigned long -stream_read_func(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count){ - HANDLE hFile = reinterpret_cast(stream->descriptor.pointer); - DWORD numberOfBytesRead; - OVERLAPPED overlapped; - overlapped.Offset = offset; - overlapped.OffsetHigh = 0; - overlapped.hEvent = NULL; - if(!ReadFile(hFile, buffer, count, &numberOfBytesRead, &overlapped)){ - return 0; - } - return numberOfBytesRead; -}; - -static void -stream_close_func(FT_Stream stream){ - HANDLE hFile = reinterpret_cast(stream->descriptor.pointer); - CloseHandle(hFile); -} -#endif - -/* - * Return a PangoFontDescription that will resolve to the font file - */ - -PangoFontDescription * -get_pango_font_description(unsigned char* filepath) { - FT_Library library; - FT_Face face; - PangoFontDescription *desc = pango_font_description_new(); -#ifdef _WIN32 - // FT_New_Face use fopen. - // Unable to find the file when supplied the multibyte string path on the Windows platform and throw error "Could not parse font file". - // This workaround fixes this by reading the font file uses win32 wide character API. - std::unique_ptr wFilepath = u8ToWide((char*)filepath); - if(!wFilepath){ - return NULL; - } - HANDLE hFile = CreateFileW( - wFilepath.get(), - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL - ); - if(!hFile){ - return NULL; - } - LARGE_INTEGER liSize; - if(!GetFileSizeEx(hFile, &liSize)) { - CloseHandle(hFile); - return NULL; - } - FT_Open_Args args; - args.flags = FT_OPEN_STREAM; - FT_StreamRec stream; - stream.base = NULL; - stream.size = liSize.QuadPart; - stream.pos = 0; - stream.descriptor.pointer = hFile; - stream.read = stream_read_func; - stream.close = stream_close_func; - args.stream = &stream; - if ( - !FT_Init_FreeType(&library) && - !FT_Open_Face(library, &args, 0, &face)) { -#else - if (!FT_Init_FreeType(&library) && !FT_New_Face(library, (const char*)filepath, 0, &face)) { -#endif - TT_OS2 *table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2); - if (table) { - char *family = get_family_name(face); - - if (!family) { - pango_font_description_free(desc); - FT_Done_Face(face); - FT_Done_FreeType(library); - - return NULL; - } - - pango_font_description_set_family(desc, family); - free(family); - pango_font_description_set_weight(desc, get_pango_weight(table->usWeightClass)); - pango_font_description_set_stretch(desc, get_pango_stretch(table->usWidthClass)); - pango_font_description_set_style(desc, get_pango_style(face->style_flags)); - - FT_Done_Face(face); - FT_Done_FreeType(library); - - return desc; - } - } - pango_font_description_free(desc); - - return NULL; -} - -/* - * Register font with the OS - */ - -bool -register_font(unsigned char *filepath) { - bool success; - - #ifdef __APPLE__ - CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false); - success = CTFontManagerRegisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL); - #elif defined(_WIN32) - std::unique_ptr wFilepath = u8ToWide((char*)filepath); - if(wFilepath){ - success = AddFontResourceExW(wFilepath.get(), FR_PRIVATE, 0) != 0; - }else{ - success = false; - } - - #else - success = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(filepath)); - #endif - - if (!success) return false; - - // Tell Pango to throw away the current FontMap and create a new one. This - // has the effect of registering the new font in Pango by re-looking up all - // font families. - pango_cairo_font_map_set_default(NULL); - - return true; -} - -/* - * Deregister font from the OS - * Note that Linux (FontConfig) can only dereregister ALL fonts at once. - */ - -bool -deregister_font(unsigned char *filepath) { - bool success; - - #ifdef __APPLE__ - CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false); - success = CTFontManagerUnregisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL); - #elif defined(_WIN32) - std::unique_ptr wFilepath = u8ToWide((char*)filepath); - if(wFilepath){ - success = RemoveFontResourceExW(wFilepath.get(), FR_PRIVATE, 0) != 0; - }else{ - success = false; - } - #else - FcConfigAppFontClear(FcConfigGetCurrent()); - success = true; - #endif - - if (!success) return false; - - // Tell Pango to throw away the current FontMap and create a new one. This - // has the effect of deregistering the font in Pango by re-looking up all - // font families. - pango_cairo_font_map_set_default(NULL); - - return true; -} diff --git a/src/register_font.h b/src/register_font.h deleted file mode 100644 index a4fcd598e..000000000 --- a/src/register_font.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -PangoFontDescription *get_pango_font_description(unsigned char *filepath); -bool register_font(unsigned char *filepath); -bool deregister_font(unsigned char *filepath); diff --git a/src/unicode.h b/src/unicode.h new file mode 100644 index 000000000..14669c1bc --- /dev/null +++ b/src/unicode.h @@ -0,0 +1,363 @@ +// This is a manually written file for the C API of unicode.zig +// TODO: delete this when Zig resurrects -femit-h +// https://github.com/ziglang/zig/issues/9698 +#pragma once + +#include +#include + +typedef enum { + SCRIPT_NONE, + SCRIPT_ADLAM, + SCRIPT_AHOM, + SCRIPT_ANATOLIAN_HIEROGLYPHS, + SCRIPT_ARABIC, + SCRIPT_ARMENIAN, + SCRIPT_AVESTAN, + SCRIPT_BALINESE, + SCRIPT_BAMUM, + SCRIPT_BASSA_VAH, + SCRIPT_BATAK, + SCRIPT_BENGALI, + SCRIPT_BHAIKSUKI, + SCRIPT_BOPOMOFO, + SCRIPT_BRAHMI, + SCRIPT_BRAILLE, + SCRIPT_BUGINESE, + SCRIPT_BUHID, + SCRIPT_CANADIAN_ABORIGINAL, + SCRIPT_CARIAN, + SCRIPT_CAUCASIAN_ALBANIAN, + SCRIPT_CHAKMA, + SCRIPT_CHAM, + SCRIPT_CHEROKEE, + SCRIPT_CHORASMIAN, + SCRIPT_COMMON, + SCRIPT_COPTIC, + SCRIPT_CUNEIFORM, + SCRIPT_CYPRIOT, + SCRIPT_CYPRO_MINOAN, + SCRIPT_CYRILLIC, + SCRIPT_DESERET, + SCRIPT_DEVANAGARI, + SCRIPT_DIVES_AKURU, + SCRIPT_DOGRA, + SCRIPT_DUPLOYAN, + SCRIPT_EGYPTIAN_HIEROGLYPHS, + SCRIPT_ELBASAN, + SCRIPT_ELYMAIC, + SCRIPT_ETHIOPIC, + SCRIPT_GARAY, + SCRIPT_GEORGIAN, + SCRIPT_GLAGOLITIC, + SCRIPT_GOTHIC, + SCRIPT_GRANTHA, + SCRIPT_GREEK, + SCRIPT_GUJARATI, + SCRIPT_GUNJALA_GONDI, + SCRIPT_GURMUKHI, + SCRIPT_GURUNG_KHEMA, + SCRIPT_HAN, + SCRIPT_HANGUL, + SCRIPT_HANIFI_ROHINGYA, + SCRIPT_HANUNOO, + SCRIPT_HATRAN, + SCRIPT_HEBREW, + SCRIPT_HIRAGANA, + SCRIPT_IMPERIAL_ARAMAIC, + SCRIPT_INHERITED, + SCRIPT_INSCRIPTIONAL_PAHLAVI, + SCRIPT_INSCRIPTIONAL_PARTHIAN, + SCRIPT_JAVANESE, + SCRIPT_KAITHI, + SCRIPT_KANNADA, + SCRIPT_KATAKANA, + SCRIPT_KAWI, + SCRIPT_KAYAH_LI, + SCRIPT_KHAROSHTHI, + SCRIPT_KHITAN_SMALL_SCRIPT, + SCRIPT_KHMER, + SCRIPT_KHOJKI, + SCRIPT_KHUDAWADI, + SCRIPT_KIRAT_RAI, + SCRIPT_LAO, + SCRIPT_LATIN, + SCRIPT_LEPCHA, + SCRIPT_LIMBU, + SCRIPT_LINEAR_A, + SCRIPT_LINEAR_B, + SCRIPT_LISU, + SCRIPT_LYCIAN, + SCRIPT_LYDIAN, + SCRIPT_MAHAJANI, + SCRIPT_MAKASAR, + SCRIPT_MALAYALAM, + SCRIPT_MANDAIC, + SCRIPT_MANICHAEAN, + SCRIPT_MARCHEN, + SCRIPT_MASARAM_GONDI, + SCRIPT_MEDEFAIDRIN, + SCRIPT_MEETEI_MAYEK, + SCRIPT_MENDE_KIKAKUI, + SCRIPT_MEROITIC_CURSIVE, + SCRIPT_MEROITIC_HIEROGLYPHS, + SCRIPT_MIAO, + SCRIPT_MODI, + SCRIPT_MONGOLIAN, + SCRIPT_MRO, + SCRIPT_MULTANI, + SCRIPT_MYANMAR, + SCRIPT_NABATAEAN, + SCRIPT_NAG_MUNDARI, + SCRIPT_NANDINAGARI, + SCRIPT_NEW_TAI_LUE, + SCRIPT_NEWA, + SCRIPT_NKO, + SCRIPT_NUSHU, + SCRIPT_NYIAKENG_PUACHUE_HMONG, + SCRIPT_OGHAM, + SCRIPT_OL_CHIKI, + SCRIPT_OL_ONAL, + SCRIPT_OLD_HUNGARIAN, + SCRIPT_OLD_ITALIC, + SCRIPT_OLD_NORTH_ARABIAN, + SCRIPT_OLD_PERMIC, + SCRIPT_OLD_PERSIAN, + SCRIPT_OLD_SOGDIAN, + SCRIPT_OLD_SOUTH_ARABIAN, + SCRIPT_OLD_TURKIC, + SCRIPT_OLD_UYGHUR, + SCRIPT_ORIYA, + SCRIPT_OSAGE, + SCRIPT_OSMANYA, + SCRIPT_PAHAWH_HMONG, + SCRIPT_PALMYRENE, + SCRIPT_PAU_CIN_HAU, + SCRIPT_PHAGS_PA, + SCRIPT_PHOENICIAN, + SCRIPT_PSALTER_PAHLAVI, + SCRIPT_REJANG, + SCRIPT_RUNIC, + SCRIPT_SAMARITAN, + SCRIPT_SAURASHTRA, + SCRIPT_SHARADA, + SCRIPT_SHAVIAN, + SCRIPT_SIDDHAM, + SCRIPT_SIGNWRITING, + SCRIPT_SINHALA, + SCRIPT_SOGDIAN, + SCRIPT_SORA_SOMPENG, + SCRIPT_SOYOMBO, + SCRIPT_SUNDANESE, + SCRIPT_SUNUWAR, + SCRIPT_SYLOTI_NAGRI, + SCRIPT_SYRIAC, + SCRIPT_TAGALOG, + SCRIPT_TAGBANWA, + SCRIPT_TAI_LE, + SCRIPT_TAI_THAM, + SCRIPT_TAI_VIET, + SCRIPT_TAKRI, + SCRIPT_TAMIL, + SCRIPT_TANGSA, + SCRIPT_TANGUT, + SCRIPT_TELUGU, + SCRIPT_THAANA, + SCRIPT_THAI, + SCRIPT_TIBETAN, + SCRIPT_TIFINAGH, + SCRIPT_TIRHUTA, + SCRIPT_TODHRI, + SCRIPT_TOTO, + SCRIPT_TULU_TIGALARI, + SCRIPT_UGARITIC, + SCRIPT_VAI, + SCRIPT_VITHKUQI, + SCRIPT_WANCHO, + SCRIPT_WARANG_CITI, + SCRIPT_YEZIDI, + SCRIPT_YI, + SCRIPT_ZANABAZAR_SQUARE +} script_t; + +extern "C" { + script_t get_script(uint32_t cp); + bool grapheme_break(uint8_t* state, uint32_t cp1, uint32_t cp2); +} + +inline hb_script_t script_to_hb_script(script_t script) { + switch (script) { + case SCRIPT_NONE: return HB_SCRIPT_UNKNOWN; + case SCRIPT_COMMON: return HB_SCRIPT_COMMON; + case SCRIPT_INHERITED: return HB_SCRIPT_INHERITED; + case SCRIPT_ADLAM: return HB_SCRIPT_ADLAM; + case SCRIPT_AHOM: return HB_SCRIPT_AHOM; + case SCRIPT_ANATOLIAN_HIEROGLYPHS: return HB_SCRIPT_ANATOLIAN_HIEROGLYPHS; + case SCRIPT_ARABIC: return HB_SCRIPT_ARABIC; + case SCRIPT_ARMENIAN: return HB_SCRIPT_ARMENIAN; + case SCRIPT_AVESTAN: return HB_SCRIPT_AVESTAN; + case SCRIPT_BALINESE: return HB_SCRIPT_BALINESE; + case SCRIPT_BAMUM: return HB_SCRIPT_BAMUM; + case SCRIPT_BASSA_VAH: return HB_SCRIPT_BASSA_VAH; + case SCRIPT_BATAK: return HB_SCRIPT_BATAK; + case SCRIPT_BENGALI: return HB_SCRIPT_BENGALI; + case SCRIPT_BHAIKSUKI: return HB_SCRIPT_BHAIKSUKI; + case SCRIPT_BOPOMOFO: return HB_SCRIPT_BOPOMOFO; + case SCRIPT_BRAHMI: return HB_SCRIPT_BRAHMI; + case SCRIPT_BRAILLE: return HB_SCRIPT_BRAILLE; + case SCRIPT_BUGINESE: return HB_SCRIPT_BUGINESE; + case SCRIPT_BUHID: return HB_SCRIPT_BUHID; + case SCRIPT_CANADIAN_ABORIGINAL: return HB_SCRIPT_CANADIAN_SYLLABICS; + case SCRIPT_CARIAN: return HB_SCRIPT_CARIAN; + case SCRIPT_CAUCASIAN_ALBANIAN: return HB_SCRIPT_CAUCASIAN_ALBANIAN; + case SCRIPT_CHAKMA: return HB_SCRIPT_CHAKMA; + case SCRIPT_CHAM: return HB_SCRIPT_CHAM; + case SCRIPT_CHEROKEE: return HB_SCRIPT_CHEROKEE; + case SCRIPT_CHORASMIAN: return HB_SCRIPT_CHORASMIAN; + case SCRIPT_COPTIC: return HB_SCRIPT_COPTIC; + case SCRIPT_CUNEIFORM: return HB_SCRIPT_CUNEIFORM; + case SCRIPT_CYPRIOT: return HB_SCRIPT_CYPRIOT; + case SCRIPT_CYPRO_MINOAN: return HB_SCRIPT_CYPRO_MINOAN; + case SCRIPT_CYRILLIC: return HB_SCRIPT_CYRILLIC; + case SCRIPT_DESERET: return HB_SCRIPT_DESERET; + case SCRIPT_DEVANAGARI: return HB_SCRIPT_DEVANAGARI; + case SCRIPT_DIVES_AKURU: return HB_SCRIPT_DIVES_AKURU; + case SCRIPT_DOGRA: return HB_SCRIPT_DOGRA; + case SCRIPT_DUPLOYAN: return HB_SCRIPT_DUPLOYAN; + case SCRIPT_EGYPTIAN_HIEROGLYPHS: return HB_SCRIPT_EGYPTIAN_HIEROGLYPHS; + case SCRIPT_ELBASAN: return HB_SCRIPT_ELBASAN; + case SCRIPT_ELYMAIC: return HB_SCRIPT_ELYMAIC; + case SCRIPT_ETHIOPIC: return HB_SCRIPT_ETHIOPIC; + case SCRIPT_GARAY: return HB_SCRIPT_GARAY; + case SCRIPT_GEORGIAN: return HB_SCRIPT_GEORGIAN; + case SCRIPT_GLAGOLITIC: return HB_SCRIPT_GLAGOLITIC; + case SCRIPT_GOTHIC: return HB_SCRIPT_GOTHIC; + case SCRIPT_GRANTHA: return HB_SCRIPT_GRANTHA; + case SCRIPT_GREEK: return HB_SCRIPT_GREEK; + case SCRIPT_GUJARATI: return HB_SCRIPT_GUJARATI; + case SCRIPT_GUNJALA_GONDI: return HB_SCRIPT_GUNJALA_GONDI; + case SCRIPT_GURMUKHI: return HB_SCRIPT_GURMUKHI; + case SCRIPT_GURUNG_KHEMA: return HB_SCRIPT_GURUNG_KHEMA; + case SCRIPT_HAN: return HB_SCRIPT_HAN; + case SCRIPT_HANGUL: return HB_SCRIPT_HANGUL; + case SCRIPT_HANIFI_ROHINGYA: return HB_SCRIPT_HANIFI_ROHINGYA; + case SCRIPT_HANUNOO: return HB_SCRIPT_HANUNOO; + case SCRIPT_HATRAN: return HB_SCRIPT_HATRAN; + case SCRIPT_HEBREW: return HB_SCRIPT_HEBREW; + case SCRIPT_HIRAGANA: return HB_SCRIPT_HIRAGANA; + case SCRIPT_IMPERIAL_ARAMAIC: return HB_SCRIPT_IMPERIAL_ARAMAIC; + case SCRIPT_INSCRIPTIONAL_PAHLAVI: return HB_SCRIPT_INSCRIPTIONAL_PAHLAVI; + case SCRIPT_INSCRIPTIONAL_PARTHIAN: return HB_SCRIPT_INSCRIPTIONAL_PARTHIAN; + case SCRIPT_JAVANESE: return HB_SCRIPT_JAVANESE; + case SCRIPT_KAITHI: return HB_SCRIPT_KAITHI; + case SCRIPT_KANNADA: return HB_SCRIPT_KANNADA; + case SCRIPT_KATAKANA: return HB_SCRIPT_KATAKANA; + case SCRIPT_KAWI: return HB_SCRIPT_KAWI; + case SCRIPT_KAYAH_LI: return HB_SCRIPT_KAYAH_LI; + case SCRIPT_KHAROSHTHI: return HB_SCRIPT_KHAROSHTHI; + case SCRIPT_KHITAN_SMALL_SCRIPT: return HB_SCRIPT_KHITAN_SMALL_SCRIPT; + case SCRIPT_KHMER: return HB_SCRIPT_KHMER; + case SCRIPT_KHOJKI: return HB_SCRIPT_KHOJKI; + case SCRIPT_KHUDAWADI: return HB_SCRIPT_KHUDAWADI; + case SCRIPT_KIRAT_RAI: return HB_SCRIPT_KIRAT_RAI; + case SCRIPT_LAO: return HB_SCRIPT_LAO; + case SCRIPT_LATIN: return HB_SCRIPT_LATIN; + case SCRIPT_LEPCHA: return HB_SCRIPT_LEPCHA; + case SCRIPT_LIMBU: return HB_SCRIPT_LIMBU; + case SCRIPT_LINEAR_A: return HB_SCRIPT_LINEAR_A; + case SCRIPT_LINEAR_B: return HB_SCRIPT_LINEAR_B; + case SCRIPT_LISU: return HB_SCRIPT_LISU; + case SCRIPT_LYCIAN: return HB_SCRIPT_LYCIAN; + case SCRIPT_LYDIAN: return HB_SCRIPT_LYDIAN; + case SCRIPT_MAHAJANI: return HB_SCRIPT_MAHAJANI; + case SCRIPT_MAKASAR: return HB_SCRIPT_MAKASAR; + case SCRIPT_MALAYALAM: return HB_SCRIPT_MALAYALAM; + case SCRIPT_MANDAIC: return HB_SCRIPT_MANDAIC; + case SCRIPT_MANICHAEAN: return HB_SCRIPT_MANICHAEAN; + case SCRIPT_MARCHEN: return HB_SCRIPT_MARCHEN; + case SCRIPT_MASARAM_GONDI: return HB_SCRIPT_MASARAM_GONDI; + case SCRIPT_MEDEFAIDRIN: return HB_SCRIPT_MEDEFAIDRIN; + case SCRIPT_MEETEI_MAYEK: return HB_SCRIPT_MEETEI_MAYEK; + case SCRIPT_MENDE_KIKAKUI: return HB_SCRIPT_MENDE_KIKAKUI; + case SCRIPT_MEROITIC_CURSIVE: return HB_SCRIPT_MEROITIC_CURSIVE; + case SCRIPT_MEROITIC_HIEROGLYPHS: return HB_SCRIPT_MEROITIC_HIEROGLYPHS; + case SCRIPT_MIAO: return HB_SCRIPT_MIAO; + case SCRIPT_MODI: return HB_SCRIPT_MODI; + case SCRIPT_MONGOLIAN: return HB_SCRIPT_MONGOLIAN; + case SCRIPT_MRO: return HB_SCRIPT_MRO; + case SCRIPT_MULTANI: return HB_SCRIPT_MULTANI; + case SCRIPT_MYANMAR: return HB_SCRIPT_MYANMAR; + case SCRIPT_NABATAEAN: return HB_SCRIPT_NABATAEAN; + case SCRIPT_NAG_MUNDARI: return HB_SCRIPT_NAG_MUNDARI; + case SCRIPT_NANDINAGARI: return HB_SCRIPT_NANDINAGARI; + case SCRIPT_NEW_TAI_LUE: return HB_SCRIPT_NEW_TAI_LUE; + case SCRIPT_NEWA: return HB_SCRIPT_NEWA; + case SCRIPT_NKO: return HB_SCRIPT_NKO; + case SCRIPT_NUSHU: return HB_SCRIPT_NUSHU; + case SCRIPT_NYIAKENG_PUACHUE_HMONG: return HB_SCRIPT_NYIAKENG_PUACHUE_HMONG; + case SCRIPT_OGHAM: return HB_SCRIPT_OGHAM; + case SCRIPT_OL_CHIKI: return HB_SCRIPT_OL_CHIKI; + case SCRIPT_OL_ONAL: return HB_SCRIPT_OL_ONAL; + case SCRIPT_OLD_HUNGARIAN: return HB_SCRIPT_OLD_HUNGARIAN; + case SCRIPT_OLD_ITALIC: return HB_SCRIPT_OLD_ITALIC; + case SCRIPT_OLD_NORTH_ARABIAN: return HB_SCRIPT_OLD_NORTH_ARABIAN; + case SCRIPT_OLD_PERMIC: return HB_SCRIPT_OLD_PERMIC; + case SCRIPT_OLD_PERSIAN: return HB_SCRIPT_OLD_PERSIAN; + case SCRIPT_OLD_SOGDIAN: return HB_SCRIPT_OLD_SOGDIAN; + case SCRIPT_OLD_SOUTH_ARABIAN: return HB_SCRIPT_OLD_SOUTH_ARABIAN; + case SCRIPT_OLD_TURKIC: return HB_SCRIPT_OLD_TURKIC; + case SCRIPT_OLD_UYGHUR: return HB_SCRIPT_OLD_UYGHUR; + case SCRIPT_ORIYA: return HB_SCRIPT_ORIYA; + case SCRIPT_OSAGE: return HB_SCRIPT_OSAGE; + case SCRIPT_OSMANYA: return HB_SCRIPT_OSMANYA; + case SCRIPT_PAHAWH_HMONG: return HB_SCRIPT_PAHAWH_HMONG; + case SCRIPT_PALMYRENE: return HB_SCRIPT_PALMYRENE; + case SCRIPT_PAU_CIN_HAU: return HB_SCRIPT_PAU_CIN_HAU; + case SCRIPT_PHAGS_PA: return HB_SCRIPT_PHAGS_PA; + case SCRIPT_PHOENICIAN: return HB_SCRIPT_PHOENICIAN; + case SCRIPT_PSALTER_PAHLAVI: return HB_SCRIPT_PSALTER_PAHLAVI; + case SCRIPT_REJANG: return HB_SCRIPT_REJANG; + case SCRIPT_RUNIC: return HB_SCRIPT_RUNIC; + case SCRIPT_SAMARITAN: return HB_SCRIPT_SAMARITAN; + case SCRIPT_SAURASHTRA: return HB_SCRIPT_SAURASHTRA; + case SCRIPT_SHARADA: return HB_SCRIPT_SHARADA; + case SCRIPT_SHAVIAN: return HB_SCRIPT_SHAVIAN; + case SCRIPT_SIDDHAM: return HB_SCRIPT_SIDDHAM; + case SCRIPT_SIGNWRITING: return HB_SCRIPT_SIGNWRITING; + case SCRIPT_SINHALA: return HB_SCRIPT_SINHALA; + case SCRIPT_SOGDIAN: return HB_SCRIPT_SOGDIAN; + case SCRIPT_SORA_SOMPENG: return HB_SCRIPT_SORA_SOMPENG; + case SCRIPT_SOYOMBO: return HB_SCRIPT_SOYOMBO; + case SCRIPT_SUNDANESE: return HB_SCRIPT_SUNDANESE; + case SCRIPT_SUNUWAR: return HB_SCRIPT_SUNUWAR; + case SCRIPT_SYLOTI_NAGRI: return HB_SCRIPT_SYLOTI_NAGRI; + case SCRIPT_SYRIAC: return HB_SCRIPT_SYRIAC; + case SCRIPT_TAGALOG: return HB_SCRIPT_TAGALOG; + case SCRIPT_TAGBANWA: return HB_SCRIPT_TAGBANWA; + case SCRIPT_TAI_LE: return HB_SCRIPT_TAI_LE; + case SCRIPT_TAI_THAM: return HB_SCRIPT_TAI_THAM; + case SCRIPT_TAI_VIET: return HB_SCRIPT_TAI_VIET; + case SCRIPT_TAKRI: return HB_SCRIPT_TAKRI; + case SCRIPT_TAMIL: return HB_SCRIPT_TAMIL; + case SCRIPT_TANGSA: return HB_SCRIPT_TANGSA; + case SCRIPT_TANGUT: return HB_SCRIPT_TANGUT; + case SCRIPT_TELUGU: return HB_SCRIPT_TELUGU; + case SCRIPT_THAANA: return HB_SCRIPT_THAANA; + case SCRIPT_THAI: return HB_SCRIPT_THAI; + case SCRIPT_TIBETAN: return HB_SCRIPT_TIBETAN; + case SCRIPT_TIFINAGH: return HB_SCRIPT_TIFINAGH; + case SCRIPT_TIRHUTA: return HB_SCRIPT_TIRHUTA; + case SCRIPT_TODHRI: return HB_SCRIPT_TODHRI; + case SCRIPT_TOTO: return HB_SCRIPT_TOTO; + case SCRIPT_TULU_TIGALARI: return HB_SCRIPT_TULU_TIGALARI; + case SCRIPT_UGARITIC: return HB_SCRIPT_UGARITIC; + case SCRIPT_VAI: return HB_SCRIPT_VAI; + case SCRIPT_VITHKUQI: return HB_SCRIPT_VITHKUQI; + case SCRIPT_WANCHO: return HB_SCRIPT_WANCHO; + case SCRIPT_WARANG_CITI: return HB_SCRIPT_WARANG_CITI; + case SCRIPT_YEZIDI: return HB_SCRIPT_YEZIDI; + case SCRIPT_YI: return HB_SCRIPT_YI; + case SCRIPT_ZANABAZAR_SQUARE: return HB_SCRIPT_ZANABAZAR_SQUARE; + default: return HB_SCRIPT_UNKNOWN; + } +} diff --git a/src/unicode.zig b/src/unicode.zig new file mode 100644 index 000000000..902e3cce4 --- /dev/null +++ b/src/unicode.zig @@ -0,0 +1,368 @@ +// This file binds the zg library to C. We use zg to: +// 1. Segment text into scripts for shaping boundaries +// 2. Find Unicode grapheme boundaries for fallback shaping +// 3. Eventually: determine emoji presentation +// (this will rewuire a PR to zg though) +const Scripts = @import("Scripts"); +const Graphemes = @import("Graphemes"); +const std = @import("std"); + +pub const Script = enum(c_int) { + none, + Adlam, + Ahom, + Anatolian_Hieroglyphs, + Arabic, + Armenian, + Avestan, + Balinese, + Bamum, + Bassa_Vah, + Batak, + Bengali, + Bhaiksuki, + Bopomofo, + Brahmi, + Braille, + Buginese, + Buhid, + Canadian_Aboriginal, + Carian, + Caucasian_Albanian, + Chakma, + Cham, + Cherokee, + Chorasmian, + Common, + Coptic, + Cuneiform, + Cypriot, + Cypro_Minoan, + Cyrillic, + Deseret, + Devanagari, + Dives_Akuru, + Dogra, + Duployan, + Egyptian_Hieroglyphs, + Elbasan, + Elymaic, + Ethiopic, + Garay, + Georgian, + Glagolitic, + Gothic, + Grantha, + Greek, + Gujarati, + Gunjala_Gondi, + Gurmukhi, + Gurung_Khema, + Han, + Hangul, + Hanifi_Rohingya, + Hanunoo, + Hatran, + Hebrew, + Hiragana, + Imperial_Aramaic, + Inherited, + Inscriptional_Pahlavi, + Inscriptional_Parthian, + Javanese, + Kaithi, + Kannada, + Katakana, + Kawi, + Kayah_Li, + Kharoshthi, + Khitan_Small_Script, + Khmer, + Khojki, + Khudawadi, + Kirat_Rai, + Lao, + Latin, + Lepcha, + Limbu, + Linear_A, + Linear_B, + Lisu, + Lycian, + Lydian, + Mahajani, + Makasar, + Malayalam, + Mandaic, + Manichaean, + Marchen, + Masaram_Gondi, + Medefaidrin, + Meetei_Mayek, + Mende_Kikakui, + Meroitic_Cursive, + Meroitic_Hieroglyphs, + Miao, + Modi, + Mongolian, + Mro, + Multani, + Myanmar, + Nabataean, + Nag_Mundari, + Nandinagari, + New_Tai_Lue, + Newa, + Nko, + Nushu, + Nyiakeng_Puachue_Hmong, + Ogham, + Ol_Chiki, + Ol_Onal, + Old_Hungarian, + Old_Italic, + Old_North_Arabian, + Old_Permic, + Old_Persian, + Old_Sogdian, + Old_South_Arabian, + Old_Turkic, + Old_Uyghur, + Oriya, + Osage, + Osmanya, + Pahawh_Hmong, + Palmyrene, + Pau_Cin_Hau, + Phags_Pa, + Phoenician, + Psalter_Pahlavi, + Rejang, + Runic, + Samaritan, + Saurashtra, + Sharada, + Shavian, + Siddham, + SignWriting, + Sinhala, + Sogdian, + Sora_Sompeng, + Soyombo, + Sundanese, + Sunuwar, + Syloti_Nagri, + Syriac, + Tagalog, + Tagbanwa, + Tai_Le, + Tai_Tham, + Tai_Viet, + Takri, + Tamil, + Tangsa, + Tangut, + Telugu, + Thaana, + Thai, + Tibetan, + Tifinagh, + Tirhuta, + Todhri, + Toto, + Tulu_Tigalari, + Ugaritic, + Vai, + Vithkuqi, + Wancho, + Warang_Citi, + Yezidi, + Yi, + Zanabazar_Square, +}; + +export fn get_script(cp: u32) callconv(.c) Script { + const script_result = Scripts.script(@as(u21, @intCast(cp))) orelse return .none; + + return switch (script_result) { + .none => .none, + .Adlam => .Adlam, + .Ahom => .Ahom, + .Anatolian_Hieroglyphs => .Anatolian_Hieroglyphs, + .Arabic => .Arabic, + .Armenian => .Armenian, + .Avestan => .Avestan, + .Balinese => .Balinese, + .Bamum => .Bamum, + .Bassa_Vah => .Bassa_Vah, + .Batak => .Batak, + .Bengali => .Bengali, + .Bhaiksuki => .Bhaiksuki, + .Bopomofo => .Bopomofo, + .Brahmi => .Brahmi, + .Braille => .Braille, + .Buginese => .Buginese, + .Buhid => .Buhid, + .Canadian_Aboriginal => .Canadian_Aboriginal, + .Carian => .Carian, + .Caucasian_Albanian => .Caucasian_Albanian, + .Chakma => .Chakma, + .Cham => .Cham, + .Cherokee => .Cherokee, + .Chorasmian => .Chorasmian, + .Common => .Common, + .Coptic => .Coptic, + .Cuneiform => .Cuneiform, + .Cypriot => .Cypriot, + .Cypro_Minoan => .Cypro_Minoan, + .Cyrillic => .Cyrillic, + .Deseret => .Deseret, + .Devanagari => .Devanagari, + .Dives_Akuru => .Dives_Akuru, + .Dogra => .Dogra, + .Duployan => .Duployan, + .Egyptian_Hieroglyphs => .Egyptian_Hieroglyphs, + .Elbasan => .Elbasan, + .Elymaic => .Elymaic, + .Ethiopic => .Ethiopic, + .Garay => .Garay, + .Georgian => .Georgian, + .Glagolitic => .Glagolitic, + .Gothic => .Gothic, + .Grantha => .Grantha, + .Greek => .Greek, + .Gujarati => .Gujarati, + .Gunjala_Gondi => .Gunjala_Gondi, + .Gurmukhi => .Gurmukhi, + .Gurung_Khema => .Gurung_Khema, + .Han => .Han, + .Hangul => .Hangul, + .Hanifi_Rohingya => .Hanifi_Rohingya, + .Hanunoo => .Hanunoo, + .Hatran => .Hatran, + .Hebrew => .Hebrew, + .Hiragana => .Hiragana, + .Imperial_Aramaic => .Imperial_Aramaic, + .Inherited => .Inherited, + .Inscriptional_Pahlavi => .Inscriptional_Pahlavi, + .Inscriptional_Parthian => .Inscriptional_Parthian, + .Javanese => .Javanese, + .Kaithi => .Kaithi, + .Kannada => .Kannada, + .Katakana => .Katakana, + .Kawi => .Kawi, + .Kayah_Li => .Kayah_Li, + .Kharoshthi => .Kharoshthi, + .Khitan_Small_Script => .Khitan_Small_Script, + .Khmer => .Khmer, + .Khojki => .Khojki, + .Khudawadi => .Khudawadi, + .Kirat_Rai => .Kirat_Rai, + .Lao => .Lao, + .Latin => .Latin, + .Lepcha => .Lepcha, + .Limbu => .Limbu, + .Linear_A => .Linear_A, + .Linear_B => .Linear_B, + .Lisu => .Lisu, + .Lycian => .Lycian, + .Lydian => .Lydian, + .Mahajani => .Mahajani, + .Makasar => .Makasar, + .Malayalam => .Malayalam, + .Mandaic => .Mandaic, + .Manichaean => .Manichaean, + .Marchen => .Marchen, + .Masaram_Gondi => .Masaram_Gondi, + .Medefaidrin => .Medefaidrin, + .Meetei_Mayek => .Meetei_Mayek, + .Mende_Kikakui => .Mende_Kikakui, + .Meroitic_Cursive => .Meroitic_Cursive, + .Meroitic_Hieroglyphs => .Meroitic_Hieroglyphs, + .Miao => .Miao, + .Modi => .Modi, + .Mongolian => .Mongolian, + .Mro => .Mro, + .Multani => .Multani, + .Myanmar => .Myanmar, + .Nabataean => .Nabataean, + .Nag_Mundari => .Nag_Mundari, + .Nandinagari => .Nandinagari, + .New_Tai_Lue => .New_Tai_Lue, + .Newa => .Newa, + .Nko => .Nko, + .Nushu => .Nushu, + .Nyiakeng_Puachue_Hmong => .Nyiakeng_Puachue_Hmong, + .Ogham => .Ogham, + .Ol_Chiki => .Ol_Chiki, + .Ol_Onal => .Ol_Onal, + .Old_Hungarian => .Old_Hungarian, + .Old_Italic => .Old_Italic, + .Old_North_Arabian => .Old_North_Arabian, + .Old_Permic => .Old_Permic, + .Old_Persian => .Old_Persian, + .Old_Sogdian => .Old_Sogdian, + .Old_South_Arabian => .Old_South_Arabian, + .Old_Turkic => .Old_Turkic, + .Old_Uyghur => .Old_Uyghur, + .Oriya => .Oriya, + .Osage => .Osage, + .Osmanya => .Osmanya, + .Pahawh_Hmong => .Pahawh_Hmong, + .Palmyrene => .Palmyrene, + .Pau_Cin_Hau => .Pau_Cin_Hau, + .Phags_Pa => .Phags_Pa, + .Phoenician => .Phoenician, + .Psalter_Pahlavi => .Psalter_Pahlavi, + .Rejang => .Rejang, + .Runic => .Runic, + .Samaritan => .Samaritan, + .Saurashtra => .Saurashtra, + .Sharada => .Sharada, + .Shavian => .Shavian, + .Siddham => .Siddham, + .SignWriting => .SignWriting, + .Sinhala => .Sinhala, + .Sogdian => .Sogdian, + .Sora_Sompeng => .Sora_Sompeng, + .Soyombo => .Soyombo, + .Sundanese => .Sundanese, + .Sunuwar => .Sunuwar, + .Syloti_Nagri => .Syloti_Nagri, + .Syriac => .Syriac, + .Tagalog => .Tagalog, + .Tagbanwa => .Tagbanwa, + .Tai_Le => .Tai_Le, + .Tai_Tham => .Tai_Tham, + .Tai_Viet => .Tai_Viet, + .Takri => .Takri, + .Tamil => .Tamil, + .Tangsa => .Tangsa, + .Tangut => .Tangut, + .Telugu => .Telugu, + .Thaana => .Thaana, + .Thai => .Thai, + .Tibetan => .Tibetan, + .Tifinagh => .Tifinagh, + .Tirhuta => .Tirhuta, + .Todhri => .Todhri, + .Toto => .Toto, + .Tulu_Tigalari => .Tulu_Tigalari, + .Ugaritic => .Ugaritic, + .Vai => .Vai, + .Vithkuqi => .Vithkuqi, + .Wancho => .Wancho, + .Warang_Citi => .Warang_Citi, + .Yezidi => .Yezidi, + .Yi => .Yi, + .Zanabazar_Square => .Zanabazar_Square, + }; +} + +export fn grapheme_break( + state: *Graphemes.IterState, + cp1: u32, + cp2: u32 +) callconv(.c) bool { + return Graphemes.graphemeBreak(@as(u21, @intCast(cp1)), @as(u21, @intCast(cp2)), state); +} diff --git a/test/canvas.test.js b/test/canvas.test.js index e992dccb6..4cb4760d7 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -14,10 +14,10 @@ const { createCanvas, createImageData, loadImage, - registerFont, Canvas, - deregisterAllFonts, - ImageData + ImageData, + fonts, + FontFace } = require('../') function assertApprox(actual, expected, tol) { @@ -55,15 +55,33 @@ describe('Canvas', function () { assert.equal(canvas.height, 14) }); - it('registerFont', function () { + it('canvas.fonts', function () { // Minimal test to make sure nothing is thrown - registerFont('./examples/pfennigFont/Pfennig.ttf', { family: 'Pfennig' }) - registerFont('./examples/pfennigFont/PfennigBold.ttf', { family: 'Pfennig', weight: 'bold' }) - + const regular = new FontFace('Pfennig1', './examples/pfennigFont/Pfennig.ttf') + const bold = new FontFace('Pfennig2', './examples/pfennigFont/PfennigBold.ttf', {weight: 'bold'}); // Test to multi byte file path support - registerFont('./examples/pfennigFont/pfennigMultiByte🚀.ttf', { family: 'Pfennig' }) + const multi = new FontFace('Pfennig3', './examples/pfennigFont/pfennigMultiByte🚀.ttf'); + + fonts.add(regular); + fonts.add(bold); + fonts.add(multi); - deregisterAllFonts() + const canvas = createCanvas(1, 1); + const ctx = canvas.getContext('2d'); + ctx.font = '8px Pfennig1'; + ctx.fillText('Pfennig', 0, 0); + ctx.font = '8px Pfennig2'; + ctx.fillText('Pfennig', 0, 0); + ctx.font = '8px Pfennig3'; + ctx.fillText('Pfennig', 0, 0); + + assert.equal(regular.status, 'loaded'); + assert.equal(bold.status, 'loaded'); + assert.equal(multi.status, 'loaded'); + + fonts.delete(multi); + fonts.delete(regular); + fonts.delete(bold); }); it('color serialization', function () { @@ -1007,6 +1025,20 @@ describe('Canvas', function () { }) describe('Context2d#measureText()', function () { + const arimo = new FontFace('Arimo', path.join(__dirname, '/fixtures/Arimo-Regular.ttf')); + + before(function () { + fonts.add(arimo); + }); + + after(function () { + fonts.delete(arimo); + }); + + // TODO: all but 'works' can be updated to use exact assertions. Those + // tests were written before custom fonts and font selelection worked + // perfectly on all OSes. + it('Context2d#measureText().width', function () { const canvas = createCanvas(20, 20) const ctx = canvas.getContext('2d') @@ -1019,31 +1051,27 @@ describe('Canvas', function () { it('works', function () { const canvas = createCanvas(20, 20) const ctx = canvas.getContext('2d') - ctx.font = '20px Arial' + ctx.font = '20px Arimo' ctx.textBaseline = 'alphabetic' let metrics = ctx.measureText('Alphabet') - // Actual value depends on font library version. Have observed values - // between 0 and 0.769. - assertApprox(metrics.alphabeticBaseline, 0.5, 0.5) - // Positive = going up from the baseline - assert.ok(metrics.actualBoundingBoxAscent > 0) - // Positive = going down from the baseline - assertApprox(metrics.actualBoundingBoxDescent, 5, 2) + assertApprox(metrics.alphabeticBaseline, 0, 0.1) + assertApprox(metrics.actualBoundingBoxAscent, 14.5, 0.1) + assertApprox(metrics.actualBoundingBoxDescent, 4.16, 0.1) ctx.textBaseline = 'bottom' metrics = ctx.measureText('Alphabet') assert.strictEqual(ctx.textBaseline, 'bottom') - assertApprox(metrics.alphabeticBaseline, 5, 2) - assert.ok(metrics.actualBoundingBoxAscent > 0) - // On the baseline or slightly above - assert.ok(metrics.actualBoundingBoxDescent <= 0) + assertApprox(metrics.alphabeticBaseline, 3.07, 0.1) + assertApprox(metrics.actualBoundingBoxAscent, 17.57, 0.1) + assertApprox(metrics.actualBoundingBoxDescent, 1.09, 0.1) }) it('actualBoundingBox is correct for left, center and right alignment (#1909)', function () { const canvas = createCanvas(0, 0) const ctx = canvas.getContext('2d') + ctx.font = '10px helvetica'; // positive actualBoundingBoxLeft indicates a distance going left from the // given alignment point. diff --git a/test/fixtures/Arimo-Regular-License.txt b/test/fixtures/Arimo-Regular-License.txt new file mode 100644 index 000000000..75b52484e --- /dev/null +++ b/test/fixtures/Arimo-Regular-License.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/test/fixtures/Arimo-Regular.ttf b/test/fixtures/Arimo-Regular.ttf new file mode 100644 index 000000000..c14e0a418 Binary files /dev/null and b/test/fixtures/Arimo-Regular.ttf differ diff --git a/test/image.test.js b/test/image.test.js index defb52115..9dd6610e6 100644 --- a/test/image.test.js +++ b/test/image.test.js @@ -14,7 +14,6 @@ const { Blob } = require('node:buffer') const { URL } = require('node:url') const { createCanvas, loadImage, rsvgVersion, Image } = require('../') -const HAVE_SVG = rsvgVersion !== undefined const pngCheckers = path.join(__dirname, '/fixtures/checkers.png') @@ -153,7 +152,6 @@ describe('Image', function () { }) it('loads SVG data URL base64', function () { - if (!HAVE_SVG) this.skip() const base64Enc = fs.readFileSync(svgTree, 'base64') const dataURL = `data:image/svg+xml;base64,${base64Enc}` return loadImage(dataURL).then((img) => { @@ -166,7 +164,6 @@ describe('Image', function () { }) it('loads SVG data URL utf8', function () { - if (!HAVE_SVG) this.skip() const utf8Encoded = fs.readFileSync(svgTree, 'utf8') const dataURL = `data:image/svg+xml;utf8,${utf8Encoded}` return loadImage(dataURL).then((img) => { @@ -377,17 +374,10 @@ describe('Image', function () { }) it('loadImage doesn\'t crash when you don\'t specify width and height', async function () { - const err = {name: 'Error'} - - // TODO: remove this when we have a static build or something - if (os.platform() !== 'win32') { - err.message = 'Width and height must be set on the svg element'; - } - - await assert.rejects(async () => { - const svg = ``; - await loadImage(Buffer.from(svg)) - }, err) + const svg = `` + const img = await loadImage(Buffer.from(svg)) + assert.equal(img.width, 1) + assert.equal(img.height, 1) }) describe('supports BMP', function () { diff --git a/test/public/tests.js b/test/public/tests.js index 1eb96279e..047bcc3e1 100644 --- a/test/public/tests.js +++ b/test/public/tests.js @@ -2698,7 +2698,7 @@ tests['measureText()'] = function (ctx) { ctx.strokeStyle = 'blue' ctx.strokeRect( // positive numbers for actualBoundingBoxLeft indicate a distance going left - x + metrics.actualBoundingBoxLeft + 0.5, + x - metrics.actualBoundingBoxLeft + 0.5, y - metrics.actualBoundingBoxAscent + 0.5, metrics.width, metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent