mirror of
https://github.com/maplibre/martin.git
synced 2024-12-19 12:51:37 +03:00
Implement dynamic font support /font/<name>/<start>-<end>
(#755)
This implements dynamic font protobuf generation, allowing users to request font ranges on the fly, and combining them in any order, e.g. `Font1,Font2,Font3`, same as with sprites and tiles This is a first iteration, without any multithreading support. In theory, this could be done far faster by generating SDFs with multiple threads. ### Current process * during init, figure out all glyphs available in each font, and store them as a bitset * during request: * combine requested bitsets to figure out which glyph should come from which font file * load those glyphs from files (using a single instance of the freetype lib) * convert them to SDFs and package them into a protobuf --------- Co-authored-by: Lucas <zhangyijunmetro@hotmail.com>
This commit is contained in:
parent
dbf25f041c
commit
9b112ae7b9
177
Cargo.lock
generated
177
Cargo.lock
generated
@ -407,6 +407,21 @@ version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -1139,6 +1154,27 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "freetype-rs"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59c337e64822dd56a3a83ed75a662a470736bdb3a9fabfb588dff276b94a4e0"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"freetype-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "freetype-sys"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643148ca6cbad6bec384b52fbe1968547d578c4efe83109e035c43a71734ff88"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs4"
|
||||
version = "0.6.6"
|
||||
@ -1701,6 +1737,7 @@ dependencies = [
|
||||
"actix-rt",
|
||||
"actix-web",
|
||||
"async-trait",
|
||||
"bit-set",
|
||||
"brotli",
|
||||
"cargo-husky",
|
||||
"clap",
|
||||
@ -1718,6 +1755,7 @@ dependencies = [
|
||||
"martin-mbtiles",
|
||||
"martin-tile-utils",
|
||||
"num_cpus",
|
||||
"pbf_font_tools",
|
||||
"pmtiles",
|
||||
"postgis",
|
||||
"postgres",
|
||||
@ -2036,6 +2074,22 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "pbf_font_tools"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67768bb2719d708e2de28cec7271dae35c717122c0fa4d9f8558ef5e7fa83db7"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"glob",
|
||||
"protobuf",
|
||||
"protobuf-codegen",
|
||||
"protoc-bin-vendored",
|
||||
"sdf_glyph_renderer",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
@ -2264,6 +2318,107 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"protobuf-support",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf-codegen"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e85514a216b1c73111d9032e26cc7a5ecb1bb3d4d9539e91fb72a4395060f78"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"once_cell",
|
||||
"protobuf",
|
||||
"protobuf-parse",
|
||||
"regex",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf-parse"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77d6fbd6697c9e531873e81cec565a85e226b99a0f10e1acc079be057fe2fcba"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap 1.9.3",
|
||||
"log",
|
||||
"protobuf",
|
||||
"protobuf-support",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf-support"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protoc-bin-vendored"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "005ca8623e5633e298ad1f917d8be0a44bcf406bf3cde3b80e63003e49a3f27d"
|
||||
dependencies = [
|
||||
"protoc-bin-vendored-linux-aarch_64",
|
||||
"protoc-bin-vendored-linux-ppcle_64",
|
||||
"protoc-bin-vendored-linux-x86_32",
|
||||
"protoc-bin-vendored-linux-x86_64",
|
||||
"protoc-bin-vendored-macos-x86_64",
|
||||
"protoc-bin-vendored-win32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protoc-bin-vendored-linux-aarch_64"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fb9fc9cce84c8694b6ea01cc6296617b288b703719b725b8c9c65f7c5874435"
|
||||
|
||||
[[package]]
|
||||
name = "protoc-bin-vendored-linux-ppcle_64"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d2a07dcf7173a04d49974930ccbfb7fd4d74df30ecfc8762cf2f895a094516"
|
||||
|
||||
[[package]]
|
||||
name = "protoc-bin-vendored-linux-x86_32"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54fef0b04fcacba64d1d80eed74a20356d96847da8497a59b0a0a436c9165b0"
|
||||
|
||||
[[package]]
|
||||
name = "protoc-bin-vendored-linux-x86_64"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8782f2ce7d43a9a5c74ea4936f001e9e8442205c244f7a3d4286bd4c37bc924"
|
||||
|
||||
[[package]]
|
||||
name = "protoc-bin-vendored-macos-x86_64"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5de656c7ee83f08e0ae5b81792ccfdc1d04e7876b1d9a38e6876a9e09e02537"
|
||||
|
||||
[[package]]
|
||||
name = "protoc-bin-vendored-win32"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9653c3ed92974e34c5a6e0a510864dab979760481714c172e0a34e437cb98804"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
@ -2654,6 +2809,16 @@ dependencies = [
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdf_glyph_renderer"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b05c114d181e20b509e03b05856cc5823bc6189d581c276fe37c5ebc5e3b3b9"
|
||||
dependencies = [
|
||||
"freetype-rs",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.9.2"
|
||||
@ -3812,6 +3977,18 @@ version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.4.1"
|
||||
|
@ -17,6 +17,7 @@ actix-rt = "2"
|
||||
actix-web = "4"
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1"
|
||||
bit-set = "0.5.3"
|
||||
brotli = "3"
|
||||
cargo-husky = { version = "1", features = ["user-hooks"], default-features = false }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
@ -35,6 +36,7 @@ log = "0.4"
|
||||
martin-mbtiles = { path = "./martin-mbtiles", version = "0.6.0", default-features = false }
|
||||
martin-tile-utils = { path = "./martin-tile-utils", version = "0.1.0" }
|
||||
num_cpus = "1"
|
||||
pbf_font_tools = { version = "2.5.0", features = ["freetype"] }
|
||||
pmtiles = { version = "0.3", features = ["mmap-async-tokio", "tilejson"] }
|
||||
postgis = "0.9"
|
||||
postgres = { version = "0.19", features = ["with-time-0_3", "with-uuid-1", "with-serde_json-1"] }
|
||||
|
4
debian/config.yaml
vendored
4
debian/config.yaml
vendored
@ -29,3 +29,7 @@ worker_processes: 8
|
||||
# - /path/to/mbtiles.mbtiles
|
||||
# sources:
|
||||
# mb-src1: /path/to/mbtiles1.mbtiles
|
||||
|
||||
# fonts:
|
||||
# - /path/to/font/file.ttf
|
||||
# - /path/to/font_dir
|
||||
|
@ -19,6 +19,9 @@ Options:
|
||||
-s, --sprite <SPRITE>
|
||||
Export a directory with SVG files as a sprite source. Can be specified multiple times
|
||||
|
||||
-f, --font <FONT>
|
||||
Export a font file or a directory with font files as a font source (recursive). Can be specified multiple times
|
||||
|
||||
-k, --keep-alive <KEEP_ALIVE>
|
||||
Connection keep alive timeout. [DEFAULT: 75]
|
||||
|
||||
@ -28,7 +31,7 @@ Options:
|
||||
-W, --workers <WORKERS>
|
||||
Number of web server workers
|
||||
|
||||
-b, --auto-bounds <BOUNDS>
|
||||
-b, --auto-bounds <AUTO_BOUNDS>
|
||||
Specify how bounds should be computed for the spatial PG tables. [DEFAULT: quick]
|
||||
|
||||
Possible values:
|
||||
|
@ -183,4 +183,10 @@ sprites:
|
||||
sources:
|
||||
# SVG images in this directory will be published as a "my_sprites" sprite source
|
||||
my_sprites: /path/to/some_dir
|
||||
|
||||
# Font configuration
|
||||
fonts:
|
||||
# A list of *.otf, *.ttf, and *.ttc font files and dirs to search recursively.
|
||||
- /path/to/font/file.ttf
|
||||
- /path/to/font_dir
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Sprite Sources
|
||||
|
||||
Given a directory with SVG images, Martin will generate a sprite -- a JSON index and a PNG image, for both low and high resolution displays. The SVG filenames without extension will be used as the sprite image IDs. The images are searched recursively in the given directory, so subdirectory names will be used as prefixes for the image IDs, e.g. `icons/bicycle.svg` will be available as `icons/bicycle` sprite image.
|
||||
Given a directory with SVG images, Martin will generate a sprite -- a JSON index and a PNG image, for both low and high resolution displays. The SVG filenames without extension will be used as the sprite image IDs. The images are searched recursively in the given directory, so subdirectory names will be used as prefixes for the image IDs, e.g. `icons/bicycle.svg` will be available as `icons/bicycle` sprite image. The sprite generation is not yet cached, and may require external reverse proxy or CDN for faster operation.
|
||||
|
||||
### API
|
||||
Martin uses [MapLibre sprites API](https://maplibre.org/maplibre-style-spec/sprite/) specification to serve sprites via several endpoints. The sprite image and index are generated on the fly, so if the sprite directory is updated, the changes will be reflected immediately.
|
||||
|
65
docs/src/37-sources-fonts.md
Normal file
65
docs/src/37-sources-fonts.md
Normal file
@ -0,0 +1,65 @@
|
||||
## Font Sources
|
||||
|
||||
Martin can serve glyph ranges from `otf`, `ttf`, and `ttc` fonts as needed by MapLibre text rendering. Martin will generate them dynamically on the fly.
|
||||
The glyph range generation is not yet cached, and may require external reverse proxy or CDN for faster operation.
|
||||
|
||||
## API
|
||||
Fonts ranges are available either for a single font, or a combination of multiple fonts. The font names are case-sensitive and should match the font name in the font file as published in the catalog. Make sure to URL-escape font names as they usually contain spaces.
|
||||
|
||||
When combining multiple fonts, the glyph range will contain glyphs from the first listed font if available, and fallback to the next font if the glyph is not available in the first font, etc. The glyph range will be empty if none of the fonts contain the glyph.
|
||||
|
||||
| Type | API | Example |
|
||||
|----------|------------------------------------------------|--------------------------------------------------------------|
|
||||
| Single | `/font/{name}/{start}-{end}` | `/font/Overpass%20Mono%20Bold/0-255` |
|
||||
| Combined | `/font/{name1},{name2},{name_n}/{start}-{end}` | `/font/Overpass%20Mono%20Bold,Overpass%20Mono%20Light/0-255` |
|
||||
|
||||
Martin will show all available fonts at the `/catalog` endpoint.
|
||||
|
||||
```shell
|
||||
curl http://127.0.0.1:3000/catalog
|
||||
{
|
||||
"fonts": {
|
||||
"Overpass Mono Bold": {
|
||||
"family": "Overpass Mono",
|
||||
"style": "Bold",
|
||||
"glyphs": 931,
|
||||
"start": 0,
|
||||
"end": 64258
|
||||
},
|
||||
"Overpass Mono Light": {
|
||||
"family": "Overpass Mono",
|
||||
"style": "Light",
|
||||
"glyphs": 931,
|
||||
"start": 0,
|
||||
"end": 64258
|
||||
},
|
||||
"Overpass Mono SemiBold": {
|
||||
"family": "Overpass Mono",
|
||||
"style": "SemiBold",
|
||||
"glyphs": 931,
|
||||
"start": 0,
|
||||
"end": 64258
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using from CLI
|
||||
|
||||
A font file or directory can be configured from the [CLI](21-run-with-cli.md) with one or more `--font` parameters.
|
||||
|
||||
```shell
|
||||
martin --font /path/to/font/file.ttf --font /path/to/font_dir
|
||||
```
|
||||
|
||||
## Configuring from Config File
|
||||
|
||||
A font directory can be configured from the config file with the `fonts` key.
|
||||
|
||||
```yaml
|
||||
# Fonts configuration
|
||||
fonts:
|
||||
# A list of *.otf, *.ttf, and *.ttc font files and dirs to search recursively.
|
||||
- /path/to/font/file.ttf
|
||||
- /path/to/font_dir
|
||||
```
|
@ -14,6 +14,7 @@
|
||||
- [MBTiles and PMTiles File Sources](34-sources-files.md)
|
||||
- [Composite Sources](35-sources-composite.md)
|
||||
- [Sprite Sources](36-sources-sprites.md)
|
||||
- [Font Sources](37-sources-fonts.md)
|
||||
- [Usage and Endpoint API](40-using-endpoints.md)
|
||||
- [Using with MapLibre](41-using-with-maplibre.md)
|
||||
- [Using with Leaflet](42-using-with-leaflet.md)
|
||||
|
@ -56,6 +56,7 @@ actix-http.workspace = true
|
||||
actix-rt.workspace = true
|
||||
actix-web.workspace = true
|
||||
async-trait.workspace = true
|
||||
bit-set.workspace = true
|
||||
brotli.workspace = true
|
||||
clap.workspace = true
|
||||
deadpool-postgres.workspace = true
|
||||
@ -68,6 +69,7 @@ log.workspace = true
|
||||
martin-mbtiles.workspace = true
|
||||
martin-tile-utils.workspace = true
|
||||
num_cpus.workspace = true
|
||||
pbf_font_tools.workspace = true
|
||||
pmtiles.workspace = true
|
||||
postgis.workspace = true
|
||||
postgres-protocol.workspace = true
|
||||
|
@ -10,7 +10,7 @@ use crate::args::srv::SrvArgs;
|
||||
use crate::args::State::{Ignore, Share, Take};
|
||||
use crate::config::Config;
|
||||
use crate::file_config::FileConfigEnum;
|
||||
use crate::{Error, Result};
|
||||
use crate::{Error, OptOneMany, Result};
|
||||
|
||||
#[derive(Parser, Debug, PartialEq, Default)]
|
||||
#[command(about, version)]
|
||||
@ -44,6 +44,9 @@ pub struct MetaArgs {
|
||||
/// Export a directory with SVG files as a sprite source. Can be specified multiple times.
|
||||
#[arg(short, long)]
|
||||
pub sprite: Vec<PathBuf>,
|
||||
/// Export a font file or a directory with font files as a font source (recursive). Can be specified multiple times.
|
||||
#[arg(short, long)]
|
||||
pub font: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
@ -81,6 +84,10 @@ impl Args {
|
||||
config.sprites = FileConfigEnum::new(self.meta.sprite);
|
||||
}
|
||||
|
||||
if !self.meta.font.is_empty() {
|
||||
config.fonts = OptOneMany::new(self.meta.font);
|
||||
}
|
||||
|
||||
cli_strings.check()
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::future::Future;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures::future::try_join_all;
|
||||
@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use subst::VariableMap;
|
||||
|
||||
use crate::file_config::{resolve_files, FileConfigEnum};
|
||||
use crate::fonts::FontSources;
|
||||
use crate::mbtiles::MbtSource;
|
||||
use crate::pg::PgConfig;
|
||||
use crate::pmtiles::PmtSource;
|
||||
@ -24,6 +25,7 @@ pub type UnrecognizedValues = HashMap<String, serde_yaml::Value>;
|
||||
pub struct ServerState {
|
||||
pub tiles: TileSources,
|
||||
pub sprites: SpriteSources,
|
||||
pub fonts: FontSources,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
@ -43,6 +45,9 @@ pub struct Config {
|
||||
#[serde(default, skip_serializing_if = "FileConfigEnum::is_none")]
|
||||
pub sprites: FileConfigEnum,
|
||||
|
||||
#[serde(default, skip_serializing_if = "OptOneMany::is_none")]
|
||||
pub fonts: OptOneMany<PathBuf>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub unrecognized: UnrecognizedValues,
|
||||
}
|
||||
@ -61,10 +66,14 @@ impl Config {
|
||||
res.extend(self.mbtiles.finalize("mbtiles.")?);
|
||||
res.extend(self.sprites.finalize("sprites.")?);
|
||||
|
||||
// TODO: support for unrecognized fonts?
|
||||
// res.extend(self.fonts.finalize("fonts.")?);
|
||||
|
||||
if self.postgres.is_empty()
|
||||
&& self.pmtiles.is_empty()
|
||||
&& self.mbtiles.is_empty()
|
||||
&& self.sprites.is_empty()
|
||||
&& self.fonts.is_empty()
|
||||
{
|
||||
Err(NoSources)
|
||||
} else {
|
||||
@ -76,6 +85,7 @@ impl Config {
|
||||
Ok(ServerState {
|
||||
tiles: self.resolve_tile_sources(idr).await?,
|
||||
sprites: SpriteSources::resolve(&mut self.sprites)?,
|
||||
fonts: FontSources::resolve(&mut self.fonts)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
365
martin/src/fonts/mod.rs
Normal file
365
martin/src/fonts/mod.rs
Normal file
@ -0,0 +1,365 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use bit_set::BitSet;
|
||||
use itertools::Itertools;
|
||||
use log::{debug, info, warn};
|
||||
use pbf_font_tools::freetype::{Face, Library};
|
||||
use pbf_font_tools::protobuf::Message;
|
||||
use pbf_font_tools::{render_sdf_glyph, Fontstack, Glyphs, PbfFontError};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::fonts::FontError::IoError;
|
||||
use crate::OptOneMany;
|
||||
|
||||
const MAX_UNICODE_CP: usize = 0xFFFF;
|
||||
const CP_RANGE_SIZE: usize = 256;
|
||||
const FONT_SIZE: usize = 24;
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
const CHAR_HEIGHT: isize = (FONT_SIZE as isize) << 6;
|
||||
const BUFFER_SIZE: usize = 3;
|
||||
const RADIUS: usize = 8;
|
||||
const CUTOFF: f64 = 0.25_f64;
|
||||
|
||||
/// Each range is 256 codepoints long, so the highest range ID is 0xFFFF / 256 = 255.
|
||||
const MAX_UNICODE_CP_RANGE_ID: usize = MAX_UNICODE_CP / CP_RANGE_SIZE;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum FontError {
|
||||
#[error("Font {0} not found")]
|
||||
FontNotFound(String),
|
||||
|
||||
#[error("Font range start ({0}) must be <= end ({1})")]
|
||||
InvalidFontRangeStartEnd(u32, u32),
|
||||
|
||||
#[error("Font range start ({0}) must be multiple of {CP_RANGE_SIZE} (e.g. 0, 256, 512, ...)")]
|
||||
InvalidFontRangeStart(u32),
|
||||
|
||||
#[error(
|
||||
"Font range end ({0}) must be multiple of {CP_RANGE_SIZE} - 1 (e.g. 255, 511, 767, ...)"
|
||||
)]
|
||||
InvalidFontRangeEnd(u32),
|
||||
|
||||
#[error("Given font range {0}-{1} is invalid. It must be {CP_RANGE_SIZE} characters long (e.g. 0-255, 256-511, ...)")]
|
||||
InvalidFontRange(u32, u32),
|
||||
|
||||
#[error("FreeType font error: {0}")]
|
||||
FreeType(#[from] pbf_font_tools::freetype::Error),
|
||||
|
||||
#[error("IO error accessing {}: {0}", .1.display())]
|
||||
IoError(std::io::Error, PathBuf),
|
||||
|
||||
#[error("Invalid font file {}", .0.display())]
|
||||
InvalidFontFilePath(PathBuf),
|
||||
|
||||
#[error("No font files found in {}", .0.display())]
|
||||
NoFontFilesFound(PathBuf),
|
||||
|
||||
#[error("Font {} could not be loaded", .0.display())]
|
||||
UnableToReadFont(PathBuf),
|
||||
|
||||
#[error("{0} in file {}", .1.display())]
|
||||
FontProcessingError(spreet::error::Error, PathBuf),
|
||||
|
||||
#[error("Font {0} is missing a family name")]
|
||||
MissingFamilyName(PathBuf),
|
||||
|
||||
#[error("PBF Font error: {0}")]
|
||||
PbfFontError(#[from] PbfFontError),
|
||||
|
||||
#[error("Error serializing protobuf: {0}")]
|
||||
ErrorSerializingProtobuf(#[from] pbf_font_tools::protobuf::Error),
|
||||
}
|
||||
|
||||
type GetGlyphInfo = (BitSet, usize, Vec<(usize, usize)>, usize, usize);
|
||||
|
||||
fn get_available_codepoints(face: &mut Face) -> Option<GetGlyphInfo> {
|
||||
let mut codepoints = BitSet::with_capacity(MAX_UNICODE_CP);
|
||||
let mut spans = Vec::new();
|
||||
let mut first: Option<usize> = None;
|
||||
let mut count = 0;
|
||||
|
||||
for cp in 0..=MAX_UNICODE_CP {
|
||||
if face.get_char_index(cp) != 0 {
|
||||
codepoints.insert(cp);
|
||||
count += 1;
|
||||
if first.is_none() {
|
||||
first = Some(cp);
|
||||
}
|
||||
} else if let Some(start) = first {
|
||||
spans.push((start, cp - 1));
|
||||
first = None;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
None
|
||||
} else {
|
||||
let start = spans[0].0;
|
||||
let end = spans[spans.len() - 1].1;
|
||||
Some((codepoints, count, spans, start, end))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FontSources {
|
||||
fonts: HashMap<String, FontSource>,
|
||||
masks: Vec<BitSet>,
|
||||
}
|
||||
|
||||
pub type FontCatalog = BTreeMap<String, CatalogFontEntry>;
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CatalogFontEntry {
|
||||
pub family: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub style: Option<String>,
|
||||
pub glyphs: usize,
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl FontSources {
|
||||
pub fn resolve(config: &mut OptOneMany<PathBuf>) -> Result<Self, FontError> {
|
||||
if config.is_empty() {
|
||||
return Ok(Self::default());
|
||||
}
|
||||
|
||||
let mut fonts = HashMap::new();
|
||||
let lib = Library::init()?;
|
||||
|
||||
for path in config.iter() {
|
||||
recurse_dirs(&lib, path.clone(), &mut fonts, true)?;
|
||||
}
|
||||
|
||||
let mut masks = Vec::with_capacity(MAX_UNICODE_CP_RANGE_ID + 1);
|
||||
|
||||
let mut bs = BitSet::with_capacity(CP_RANGE_SIZE);
|
||||
for v in 0..=MAX_UNICODE_CP {
|
||||
bs.insert(v);
|
||||
if v % CP_RANGE_SIZE == (CP_RANGE_SIZE - 1) {
|
||||
masks.push(bs);
|
||||
bs = BitSet::with_capacity(CP_RANGE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { fonts, masks })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_catalog(&self) -> FontCatalog {
|
||||
self.fonts
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.catalog_entry.clone()))
|
||||
.sorted_by(|(a, _), (b, _)| a.cmp(b))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Given a list of IDs in a format "id1,id2,id3", return a combined font.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub fn get_font_range(&self, ids: &str, start: u32, end: u32) -> Result<Vec<u8>, FontError> {
|
||||
if start > end {
|
||||
return Err(FontError::InvalidFontRangeStartEnd(start, end));
|
||||
}
|
||||
if start % (CP_RANGE_SIZE as u32) != 0 {
|
||||
return Err(FontError::InvalidFontRangeStart(start));
|
||||
}
|
||||
if end % (CP_RANGE_SIZE as u32) != (CP_RANGE_SIZE as u32 - 1) {
|
||||
return Err(FontError::InvalidFontRangeEnd(end));
|
||||
}
|
||||
if (end - start) != (CP_RANGE_SIZE as u32 - 1) {
|
||||
return Err(FontError::InvalidFontRange(start, end));
|
||||
}
|
||||
|
||||
let mut needed = self.masks[(start as usize) / CP_RANGE_SIZE].clone();
|
||||
let fonts = ids
|
||||
.split(',')
|
||||
.filter_map(|id| match self.fonts.get(id) {
|
||||
None => Some(Err(FontError::FontNotFound(id.to_string()))),
|
||||
Some(v) => {
|
||||
let mut ds = needed.clone();
|
||||
ds.intersect_with(&v.codepoints);
|
||||
if ds.is_empty() {
|
||||
None
|
||||
} else {
|
||||
needed.difference_with(&v.codepoints);
|
||||
Some(Ok((id, v, ds)))
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, FontError>>()?;
|
||||
|
||||
if fonts.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let lib = Library::init()?;
|
||||
let mut stack = Fontstack::new();
|
||||
|
||||
for (id, font, ds) in fonts {
|
||||
if stack.has_name() {
|
||||
let name = stack.mut_name();
|
||||
name.push_str(", ");
|
||||
name.push_str(id);
|
||||
} else {
|
||||
stack.set_name(id.to_string());
|
||||
}
|
||||
|
||||
let face = lib.new_face(&font.path, font.face_index)?;
|
||||
|
||||
// FreeType conventions: char width or height of zero means "use the same value"
|
||||
// and setting both resolution values to zero results in the default value
|
||||
// of 72 dpi.
|
||||
//
|
||||
// See https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#ft_set_char_size
|
||||
// and https://www.freetype.org/freetype2/docs/tutorial/step1.html for details.
|
||||
face.set_char_size(0, CHAR_HEIGHT, 0, 0)?;
|
||||
|
||||
for cp in &ds {
|
||||
let glyph = render_sdf_glyph(&face, cp as u32, BUFFER_SIZE, RADIUS, CUTOFF)?;
|
||||
stack.glyphs.push(glyph);
|
||||
}
|
||||
}
|
||||
|
||||
stack.set_range(format!("{start}-{end}"));
|
||||
|
||||
let mut glyphs = Glyphs::new();
|
||||
glyphs.stacks.push(stack);
|
||||
let mut result = Vec::new();
|
||||
glyphs.write_to_vec(&mut result)?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FontSource {
|
||||
path: PathBuf,
|
||||
face_index: isize,
|
||||
codepoints: BitSet,
|
||||
catalog_entry: CatalogFontEntry,
|
||||
}
|
||||
|
||||
fn recurse_dirs(
|
||||
lib: &Library,
|
||||
path: PathBuf,
|
||||
fonts: &mut HashMap<String, FontSource>,
|
||||
is_top_level: bool,
|
||||
) -> Result<(), FontError> {
|
||||
let start_count = fonts.len();
|
||||
if path.is_dir() {
|
||||
for dir_entry in path
|
||||
.read_dir()
|
||||
.map_err(|e| IoError(e, path.clone()))?
|
||||
.flatten()
|
||||
{
|
||||
recurse_dirs(lib, dir_entry.path(), fonts, false)?;
|
||||
}
|
||||
if is_top_level && fonts.len() == start_count {
|
||||
return Err(FontError::NoFontFilesFound(path));
|
||||
}
|
||||
} else {
|
||||
if path
|
||||
.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
.is_some_and(|e| ["otf", "ttf", "ttc"].contains(&e))
|
||||
{
|
||||
parse_font(lib, fonts, path.clone())?;
|
||||
}
|
||||
if is_top_level && fonts.len() == start_count {
|
||||
return Err(FontError::InvalidFontFilePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_font(
|
||||
lib: &Library,
|
||||
fonts: &mut HashMap<String, FontSource>,
|
||||
path: PathBuf,
|
||||
) -> Result<(), FontError> {
|
||||
static RE_SPACES: OnceLock<Regex> = OnceLock::new();
|
||||
|
||||
let mut face = lib.new_face(&path, 0)?;
|
||||
let num_faces = face.num_faces() as isize;
|
||||
for face_index in 0..num_faces {
|
||||
if face_index > 0 {
|
||||
face = lib.new_face(&path, face_index)?;
|
||||
}
|
||||
let Some(family) = face.family_name() else {
|
||||
return Err(FontError::MissingFamilyName(path));
|
||||
};
|
||||
let mut name = family.clone();
|
||||
let style = face.style_name();
|
||||
if let Some(style) = &style {
|
||||
name.push(' ');
|
||||
name.push_str(style);
|
||||
}
|
||||
// Make sure font name has no slashes or commas, replacing them with spaces and de-duplicating spaces
|
||||
name = name.replace(['/', ','], " ");
|
||||
name = RE_SPACES
|
||||
.get_or_init(|| Regex::new(r"\s+").unwrap())
|
||||
.replace_all(name.as_str(), " ")
|
||||
.to_string();
|
||||
|
||||
match fonts.entry(name) {
|
||||
Entry::Occupied(v) => {
|
||||
warn!(
|
||||
"Ignoring duplicate font {} from {} because it was already configured from {}",
|
||||
v.key(),
|
||||
path.display(),
|
||||
v.get().path.display()
|
||||
);
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
let key = v.key();
|
||||
let Some((codepoints, glyphs, ranges, start, end)) =
|
||||
get_available_codepoints(&mut face)
|
||||
else {
|
||||
warn!(
|
||||
"Ignoring font {key} from {} because it has no available glyphs",
|
||||
path.display()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
info!(
|
||||
"Configured font {key} with {glyphs} glyphs ({start:04X}-{end:04X}) from {}",
|
||||
path.display()
|
||||
);
|
||||
debug!(
|
||||
"Available font ranges: {}",
|
||||
ranges
|
||||
.iter()
|
||||
.map(|(s, e)| if s == e {
|
||||
format!("{s:02X}")
|
||||
} else {
|
||||
format!("{s:02X}-{e:02X}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
);
|
||||
|
||||
v.insert(FontSource {
|
||||
path: path.clone(),
|
||||
face_index,
|
||||
codepoints,
|
||||
catalog_entry: CatalogFontEntry {
|
||||
family,
|
||||
style,
|
||||
glyphs,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
pub mod args;
|
||||
mod config;
|
||||
pub mod file_config;
|
||||
pub mod fonts;
|
||||
pub mod mbtiles;
|
||||
pub mod pg;
|
||||
pub mod pmtiles;
|
||||
|
@ -24,6 +24,7 @@ use serde::{Deserialize, Serialize};
|
||||
use tilejson::{tilejson, TileJSON};
|
||||
|
||||
use crate::config::ServerState;
|
||||
use crate::fonts::{FontCatalog, FontError, FontSources};
|
||||
use crate::source::{Source, TileCatalog, TileSources, UrlQuery};
|
||||
use crate::sprites::{SpriteCatalog, SpriteError, SpriteSources};
|
||||
use crate::srv::config::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT};
|
||||
@ -49,6 +50,7 @@ static SUPPORTED_ENCODINGS: &[HeaderEnc] = &[
|
||||
pub struct Catalog {
|
||||
pub tiles: TileCatalog,
|
||||
pub sprites: SpriteCatalog,
|
||||
pub fonts: FontCatalog,
|
||||
}
|
||||
|
||||
impl Catalog {
|
||||
@ -56,6 +58,7 @@ impl Catalog {
|
||||
Ok(Self {
|
||||
tiles: state.tiles.get_catalog(),
|
||||
sprites: state.sprites.get_catalog()?,
|
||||
fonts: state.fonts.get_catalog(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -86,6 +89,19 @@ pub fn map_sprite_error(e: SpriteError) -> actix_web::Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_font_error(e: FontError) -> actix_web::Error {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use FontError::*;
|
||||
match e {
|
||||
FontNotFound(_) => ErrorNotFound(e.to_string()),
|
||||
InvalidFontRangeStartEnd(_, _)
|
||||
| InvalidFontRangeStart(_)
|
||||
| InvalidFontRangeEnd(_)
|
||||
| InvalidFontRange(_, _) => ErrorBadRequest(e.to_string()),
|
||||
_ => map_internal_error(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Root path will eventually have a web front. For now, just a stub.
|
||||
#[route("/", method = "GET", method = "HEAD")]
|
||||
#[allow(clippy::unused_async)]
|
||||
@ -147,6 +163,28 @@ async fn get_sprite_json(
|
||||
Ok(HttpResponse::Ok().json(sheet.get_index()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct FontRequest {
|
||||
fontstack: String,
|
||||
start: u32,
|
||||
end: u32,
|
||||
}
|
||||
|
||||
#[route(
|
||||
"/font/{fontstack}/{start}-{end}",
|
||||
method = "GET",
|
||||
wrap = "middleware::Compress::default()"
|
||||
)]
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn get_font(path: Path<FontRequest>, fonts: Data<FontSources>) -> Result<HttpResponse> {
|
||||
let data = fonts
|
||||
.get_font_range(&path.fontstack, path.start, path.end)
|
||||
.map_err(map_font_error)?;
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/x-protobuf")
|
||||
.body(data))
|
||||
}
|
||||
|
||||
#[route(
|
||||
"/{source_ids}",
|
||||
method = "GET",
|
||||
@ -427,7 +465,8 @@ pub fn router(cfg: &mut web::ServiceConfig) {
|
||||
.service(git_source_info)
|
||||
.service(get_tile)
|
||||
.service(get_sprite_json)
|
||||
.service(get_sprite_png);
|
||||
.service(get_sprite_png)
|
||||
.service(get_font);
|
||||
}
|
||||
|
||||
/// Create a new initialized Actix `App` instance together with the listening address.
|
||||
@ -447,6 +486,7 @@ pub fn new_server(config: SrvConfig, state: ServerState) -> crate::Result<(Serve
|
||||
App::new()
|
||||
.app_data(Data::new(state.tiles.clone()))
|
||||
.app_data(Data::new(state.sprites.clone()))
|
||||
.app_data(Data::new(state.fonts.clone()))
|
||||
.app_data(Data::new(catalog.clone()))
|
||||
.wrap(cors_middleware)
|
||||
.wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))
|
||||
|
@ -3,6 +3,7 @@ use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::file_config::FileError;
|
||||
use crate::fonts::FontError;
|
||||
use crate::pg::PgError;
|
||||
use crate::sprites::SpriteError;
|
||||
|
||||
@ -59,4 +60,7 @@ pub enum Error {
|
||||
|
||||
#[error("{0}")]
|
||||
SpriteError(#[from] SpriteError),
|
||||
|
||||
#[error("{0}")]
|
||||
FontError(#[from] FontError),
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ async fn mbt_get_catalog() {
|
||||
content_type: image/webp
|
||||
name: ne2sr
|
||||
sprites: {}
|
||||
fonts: {}
|
||||
"###);
|
||||
}
|
||||
|
||||
@ -100,6 +101,7 @@ async fn mbt_get_catalog_gzip() {
|
||||
content_type: image/webp
|
||||
name: ne2sr
|
||||
sprites: {}
|
||||
fonts: {}
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,7 @@ postgres:
|
||||
content_type: application/x-protobuf
|
||||
description: public.table_source_multiple_geom.geom2
|
||||
sprites: {}
|
||||
fonts: {}
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ async fn pmt_get_catalog() {
|
||||
stamen_toner__raster_CC-BY-ODbL_z3:
|
||||
content_type: image/png
|
||||
sprites: {}
|
||||
fonts: {}
|
||||
"###);
|
||||
}
|
||||
|
||||
@ -72,6 +73,7 @@ async fn pmt_get_catalog_gzip() {
|
||||
p_png:
|
||||
content_type: image/png
|
||||
sprites: {}
|
||||
fonts: {}
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -168,3 +168,7 @@ sprites:
|
||||
paths: tests/fixtures/sprites/src1
|
||||
sources:
|
||||
mysrc: tests/fixtures/sprites/src2
|
||||
|
||||
fonts:
|
||||
- tests/fixtures/fonts/overpass-mono-regular.ttf
|
||||
- tests/fixtures/fonts
|
||||
|
@ -163,5 +163,29 @@
|
||||
"description": "Major cities from Natural Earth data"
|
||||
}
|
||||
},
|
||||
"sprites": {}
|
||||
"sprites": {
|
||||
"src1": {
|
||||
"images": [
|
||||
"another_bicycle",
|
||||
"bear",
|
||||
"sub/circle"
|
||||
]
|
||||
}
|
||||
},
|
||||
"fonts": {
|
||||
"Overpass Mono Light": {
|
||||
"family": "Overpass Mono",
|
||||
"style": "Light",
|
||||
"glyphs": 931,
|
||||
"start": 0,
|
||||
"end": 64258
|
||||
},
|
||||
"Overpass Mono Regular": {
|
||||
"family": "Overpass Mono",
|
||||
"style": "Regular",
|
||||
"glyphs": 931,
|
||||
"start": 0,
|
||||
"end": 64258
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,5 +53,21 @@
|
||||
"sub/circle"
|
||||
]
|
||||
}
|
||||
},
|
||||
"fonts": {
|
||||
"Overpass Mono Light": {
|
||||
"family": "Overpass Mono",
|
||||
"style": "Light",
|
||||
"glyphs": 931,
|
||||
"start": 0,
|
||||
"end": 64258
|
||||
},
|
||||
"Overpass Mono Regular": {
|
||||
"family": "Overpass Mono",
|
||||
"style": "Regular",
|
||||
"glyphs": 931,
|
||||
"start": 0,
|
||||
"end": 64258
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
tests/expected/configured/font_1.pbf
Normal file
BIN
tests/expected/configured/font_1.pbf
Normal file
Binary file not shown.
BIN
tests/expected/configured/font_2.pbf
Normal file
BIN
tests/expected/configured/font_2.pbf
Normal file
Binary file not shown.
BIN
tests/expected/configured/font_3.pbf
Normal file
BIN
tests/expected/configured/font_3.pbf
Normal file
Binary file not shown.
@ -212,3 +212,7 @@ mbtiles:
|
||||
world_cities_diff: tests/fixtures/mbtiles/world_cities_diff.mbtiles
|
||||
world_cities_modified: tests/fixtures/mbtiles/world_cities_modified.mbtiles
|
||||
zoomed_world_cities: tests/fixtures/mbtiles/zoomed_world_cities.mbtiles
|
||||
sprites: tests/fixtures/sprites/src1
|
||||
fonts:
|
||||
- tests/fixtures/fonts/overpass-mono-regular.ttf
|
||||
- tests/fixtures/fonts
|
||||
|
@ -164,3 +164,6 @@ sprites:
|
||||
paths: tests/fixtures/sprites/src1
|
||||
sources:
|
||||
mysrc: tests/fixtures/sprites/src2
|
||||
fonts:
|
||||
- tests/fixtures/fonts/overpass-mono-regular.ttf
|
||||
- tests/fixtures/fonts
|
||||
|
BIN
tests/fixtures/fonts/overpass-mono-regular.ttf
vendored
Executable file
BIN
tests/fixtures/fonts/overpass-mono-regular.ttf
vendored
Executable file
Binary file not shown.
BIN
tests/fixtures/fonts/sub_dir/overpass-mono-light.otf
vendored
Executable file
BIN
tests/fixtures/fonts/sub_dir/overpass-mono-light.otf
vendored
Executable file
Binary file not shown.
@ -97,6 +97,15 @@ test_png()
|
||||
fi
|
||||
}
|
||||
|
||||
test_font()
|
||||
{
|
||||
FILENAME="$TEST_OUT_DIR/$1.pbf"
|
||||
URL="$MARTIN_URL/$2"
|
||||
|
||||
echo "Testing $(basename "$FILENAME") from $URL"
|
||||
$CURL "$URL" > "$FILENAME"
|
||||
}
|
||||
|
||||
# Delete a line from a file $1 that matches parameter $2
|
||||
remove_line()
|
||||
{
|
||||
@ -127,6 +136,7 @@ validate_log()
|
||||
|
||||
# Make sure the log has just the expected warnings, remove them, and test that there are no other ones
|
||||
test_log_has_str "$LOG_FILE" 'WARN martin::pg::table_source] Table public.table_source has no spatial index on column geom'
|
||||
test_log_has_str "$LOG_FILE" 'WARN martin::fonts] Ignoring duplicate font Overpass Mono Regular from tests/fixtures/fonts/overpass-mono-regular.ttf because it was already configured from tests/fixtures/fonts/overpass-mono-regular.ttf'
|
||||
|
||||
echo "Checking for no other warnings or errors in the log"
|
||||
if grep -e ' ERROR ' -e ' WARN ' "$LOG_FILE"; then
|
||||
@ -153,7 +163,7 @@ echo "Test auto configured Martin"
|
||||
TEST_OUT_DIR="$(dirname "$0")/output/auto"
|
||||
mkdir -p "$TEST_OUT_DIR"
|
||||
|
||||
ARG=(--default-srid 900913 --auto-bounds calc --save-config "$(dirname "$0")/output/generated_config.yaml" tests/fixtures/mbtiles tests/fixtures/pmtiles)
|
||||
ARG=(--default-srid 900913 --auto-bounds calc --save-config "$(dirname "$0")/output/generated_config.yaml" tests/fixtures/mbtiles tests/fixtures/pmtiles --sprite tests/fixtures/sprites/src1 --font tests/fixtures/fonts/overpass-mono-regular.ttf --font tests/fixtures/fonts)
|
||||
set -x
|
||||
$MARTIN_BIN "${ARG[@]}" 2>&1 | tee "${TMP_DIR}/test_log_1.txt" &
|
||||
PROCESS_ID=`jobs -p`
|
||||
@ -268,6 +278,10 @@ test_png spr_cmp sprite/src1,mysrc.png
|
||||
test_jsn spr_cmp_2x sprite/src1,mysrc@2x.json
|
||||
test_png spr_cmp_2x sprite/src1,mysrc@2x.png
|
||||
|
||||
test_font font_1 font/Overpass%20Mono%20Light/0-255
|
||||
test_font font_2 font/Overpass%20Mono%20Regular/0-255
|
||||
test_font font_3 font/Overpass%20Mono%20Regular,Overpass%20Mono%20Light/0-255
|
||||
|
||||
kill_process $PROCESS_ID
|
||||
validate_log "${TMP_DIR}/test_log_2.txt"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user