mirror of
https://github.com/wassname/greater_tables_project.git
synced 2026-08-04 12:54:07 +08:00
pre build changes for gtfont
This commit is contained in:
@@ -166,3 +166,6 @@ cython_debug/
|
||||
#.idea/
|
||||
docs/books.bib
|
||||
docs/library.bib
|
||||
|
||||
# rust
|
||||
gtfont/target
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
Versions
|
||||
==========
|
||||
Versions and Change Log
|
||||
==========================
|
||||
|
||||
5.0.0
|
||||
-------
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
__version__ = '5.0.0'
|
||||
__project__ = 'greater_tables'
|
||||
__author__ = 'Stephen J Mildenhall'
|
||||
|
||||
from . _version import __version__
|
||||
from . core import GT
|
||||
from . fabrications import Fabricator
|
||||
from . etcher import Etcher
|
||||
|
||||
# from . gtbreaks import Breakability
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# file generated by setuptools-scm
|
||||
# don't change, don't track in version control
|
||||
|
||||
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
||||
else:
|
||||
VERSION_TUPLE = object
|
||||
|
||||
version: str
|
||||
__version__: str
|
||||
__version_tuple__: VERSION_TUPLE
|
||||
version_tuple: VERSION_TUPLE
|
||||
|
||||
__version__ = version = '5.0.0'
|
||||
__version_tuple__ = version_tuple = (5, 0, 0)
|
||||
@@ -51,7 +51,7 @@ class Configurator(BaseModel):
|
||||
default_ratio_str: str = Field(
|
||||
"{x:.1%}", description="Format f-string for ratios. Example: '{x:.1%}'"
|
||||
)
|
||||
default_formatter: Optional[str] = Field(
|
||||
default_formatter: Optional[Union[str, Callable[[Any, str], str]]] = Field(
|
||||
None, description="Optional fallback formatter f-string"
|
||||
)
|
||||
|
||||
|
||||
+18
-11
@@ -376,14 +376,14 @@ class GT(object):
|
||||
|
||||
# figure the default formatter (used in conjunction with raw columns)
|
||||
if self.config.default_formatter is None:
|
||||
self.default_formatter = self.default_formatter
|
||||
self.default_formatter = self._default_formatter
|
||||
else:
|
||||
assert callable(
|
||||
config.default_formatter), 'config.default_formatter must be callable'
|
||||
self.config.default_formatter), 'config.default_formatter must be callable'
|
||||
|
||||
def wrapped_default_formatter(x):
|
||||
try:
|
||||
return config.default_formatter(x)
|
||||
return self.config.default_formatter(x)
|
||||
except ValueError:
|
||||
return str(x)
|
||||
self.default_formatter = wrapped_default_formatter
|
||||
@@ -513,9 +513,9 @@ class GT(object):
|
||||
if tabs is None:
|
||||
self.tabs = None
|
||||
elif isinstance(tabs, (int, float)):
|
||||
self.tabs = (tabs,) * self.ncols
|
||||
self.tabs = (tabs,) * (self.nindex + self.ncols)
|
||||
elif isinstance(tabs, (np.ndarray, pd.Series, list, tuple)):
|
||||
if len(tabs) == self.ncols:
|
||||
if len(tabs) == self.nindex + self.ncols:
|
||||
self.tabs = tabs # Already iterable and right length, self.tabs = as is
|
||||
else:
|
||||
logger.error(
|
||||
@@ -579,7 +579,7 @@ class GT(object):
|
||||
if self.config.tikz_escape_tex:
|
||||
self.df_tex = Escaping.escape_df_tex(self.df)
|
||||
else:
|
||||
self.df_tex
|
||||
self.df_tex = self.df
|
||||
|
||||
def __repr__(self):
|
||||
"""Basic representation."""
|
||||
@@ -710,8 +710,8 @@ class GT(object):
|
||||
logger.debug(f'AttributeError {e}')
|
||||
return str(x)
|
||||
|
||||
def default_formatter(self, x):
|
||||
"""Default universal formatter for other types (GTP re-write of above cluster)."""
|
||||
def _default_formatter(self, x):
|
||||
"""Default universal formatter for other types."""
|
||||
try:
|
||||
f = float(x)
|
||||
except (TypeError, ValueError):
|
||||
@@ -920,7 +920,12 @@ class GT(object):
|
||||
def tex_knowledge_df(self):
|
||||
"""Uber source of information for tex formatting."""
|
||||
if self._tex_knowledge_df is None:
|
||||
self._tex_knowledge_df = self.estimate_column_widths_by_mode('tex')
|
||||
if (all(self.df_tex.index == self.df_html.index)
|
||||
and all(self.df_tex.columns == self.df_html.columns)
|
||||
and all(self.df_tex == self.df_html)):
|
||||
self._tex_knowledge_df = self.html_knowledge_df
|
||||
else:
|
||||
self._tex_knowledge_df = self.estimate_column_widths_by_mode('tex')
|
||||
return self._tex_knowledge_df
|
||||
|
||||
def width_report(self):
|
||||
@@ -959,7 +964,7 @@ class GT(object):
|
||||
return bit
|
||||
|
||||
def estimate_column_widths_by_mode(self, mode):
|
||||
"""
|
||||
r"""
|
||||
Return dataframe of width information: three modes for text, html, and tex.
|
||||
|
||||
Mode adjusts which df is used and how widths are estimated
|
||||
@@ -2027,7 +2032,9 @@ class GT(object):
|
||||
def make_svg(self):
|
||||
"""Render tikz into svg text."""
|
||||
tz = Etcher(self._repr_latex_(),
|
||||
file_name=self.df_id, debug=self.config.debug)
|
||||
self.config.table_font_pt_size,
|
||||
file_name=self.df_id
|
||||
)
|
||||
p = tz.file_path.with_suffix('.svg')
|
||||
if not p.exists():
|
||||
try:
|
||||
|
||||
+23
-16
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
|
||||
class Etcher:
|
||||
"""Create PDF and SVG files from Tikz blocks."""
|
||||
# Full TeX preamble to generate a .fmt if needed
|
||||
_tex_template_full = r"""\documentclass[10pt, border=5mm]{standalone}
|
||||
_tex_template_full = r"""\documentclass[11pt, border=5mm]{standalone}
|
||||
\usepackage{newtxtext,newtxmath} % gpt recommended like STIX
|
||||
%\usepackage{mathptmx} % gpt like times roman
|
||||
\usepackage{amsfonts}
|
||||
@@ -44,28 +44,36 @@ class Etcher:
|
||||
\newcommand{{\I}}{{\vphantom{{lp}}}} % fka grtspacer
|
||||
\def\dfrac{{\displaystyle\frac}}
|
||||
\def\dint{{\displaystyle\int}}
|
||||
|
||||
\begin{{document}}
|
||||
|
||||
{tikz_begin}{tikz_code}{tikz_end}
|
||||
|
||||
\end{{document}}
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, txt, file_name='', base_path='.', tex_engine='pdflatex', debug=False):
|
||||
def __init__(self, txt, font_size=11, file_name='', base_path='.', tex_engine='pdflatex'):
|
||||
"""Create object from txt, a TeX blob containing a tikzpicture."""
|
||||
self.txt = txt
|
||||
self.font_size = font_size
|
||||
self.tex_engine = tex_engine
|
||||
self.base_path = Path(base_path).resolve()
|
||||
self.out_path = self.base_path / 'tikz'
|
||||
self.out_path.mkdir(exist_ok=True)
|
||||
file_name = file_name or txt_short_hash(txt)
|
||||
self.file_path = self.out_path / file_name
|
||||
self.format_file = self.out_path / 'tikz_format.fmt'
|
||||
self.debug = debug
|
||||
self.format_file = self.out_path / f'tikz_format-{self.font_size}.fmt'
|
||||
|
||||
def split_tikz(self):
|
||||
"""Split text to extract the TikZ picture."""
|
||||
return re.split(r'(\\begin{tikz(?:cd|picture)}|\\end{tikz(?:cd|picture)})', self.txt)
|
||||
|
||||
def unlink_format_file(self):
|
||||
"""Unlink the format file to force a rebuild."""
|
||||
if self.format_file.exists():
|
||||
self.format_file.unlink()
|
||||
|
||||
def ensure_format_file(self):
|
||||
"""Create format file for faster compilation if missing."""
|
||||
if self.format_file.exists():
|
||||
@@ -84,8 +92,10 @@ class Etcher:
|
||||
(self.file_path.parent / 'make_format.bat').write_text(" ".join(cmd), encoding='utf-8')
|
||||
self.run_command(cmd, raise_on_error=True, cwd=self.out_path)
|
||||
# tidy up ... to some extent
|
||||
# tmp.unlink()
|
||||
(self.out_path / f'{self.format_file.stem}.log').unlink()
|
||||
for ext in ('.aux', '.log'):
|
||||
path = tmp.with_suffix(ext)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
logger.info('...success...format file built', self.format_file.resolve())
|
||||
|
||||
def process_tikz(self):
|
||||
@@ -112,8 +122,7 @@ class Etcher:
|
||||
str(tex_path)
|
||||
]
|
||||
(tex_path.parent / 'make_tikz.bat').write_text(" ".join(tex_cmd), encoding='utf-8')
|
||||
if self.debug:
|
||||
logger.info("Running:", " ".join(tex_cmd))
|
||||
logger.info("Running:", " ".join(tex_cmd))
|
||||
if self.run_command(tex_cmd):
|
||||
raise ValueError('TeX failed to compile, not pdf or svg output.')
|
||||
# no tidying up
|
||||
@@ -125,15 +134,13 @@ class Etcher:
|
||||
str(pdf_path),
|
||||
str(svg_path)
|
||||
]
|
||||
if self.debug:
|
||||
logger.info("Running:", " ".join(svg_cmd))
|
||||
logger.info("Running:", " ".join(svg_cmd))
|
||||
self.run_command(svg_cmd, raise_on_error=True)
|
||||
|
||||
if not self.debug:
|
||||
for ext in ('.tex', '.aux', '.log', '.pdf'):
|
||||
path = tex_path.with_suffix(ext)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
for ext in ('.aux', '.log', '.pdf'):
|
||||
path = tex_path.with_suffix(ext)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
|
||||
def display(self):
|
||||
"""Display the SVG in Jupyter."""
|
||||
@@ -143,7 +150,7 @@ class Etcher:
|
||||
"""Run command with subprocess and show output."""
|
||||
with Popen(command, cwd=cwd, stdout=PIPE, stderr=PIPE, universal_newlines=True) as p:
|
||||
stdout, stderr = p.communicate()
|
||||
if stdout and self.debug:
|
||||
if stdout:
|
||||
logger.info('Run command output ends\n', stdout.strip()[-250:])
|
||||
if stdout:
|
||||
if stdout.find('no output PDF file produced') > 0:
|
||||
|
||||
@@ -302,7 +302,7 @@ class TextLength:
|
||||
"‘": 0.333,
|
||||
"{": 0.48,
|
||||
"}": 0.48,
|
||||
"-": 0.333,
|
||||
"-": 0.5, # 0.333,
|
||||
}
|
||||
char_width = {c: w for chars, w in width_table.items() for c in chars}
|
||||
return char_width.get(c, 0.6)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "gtfont"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.21", features = ["extension-module", "abi3-py38"] }
|
||||
fontdue = "0.8"
|
||||
|
||||
[package.metadata.maturin]
|
||||
name = "gtfont"
|
||||
|
||||
[env]
|
||||
PYO3_USE_ABI3_FORWARD_COMPATIBILITY = "1"
|
||||
@@ -0,0 +1,59 @@
|
||||
use fontdue::Font;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass]
|
||||
struct FontMeasurer {
|
||||
font: Font,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl FontMeasurer {
|
||||
#[new]
|
||||
fn new(font_bytes: &[u8]) -> PyResult<Self> {
|
||||
let font = Font::from_bytes(font_bytes, fontdue::FontSettings::default())
|
||||
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Font error: {e}")))?;
|
||||
Ok(Self { font })
|
||||
}
|
||||
|
||||
fn measure(&self, text: &str, px: f32) -> f32 {
|
||||
text.chars()
|
||||
.map(|c| self.font.metrics(c, px).advance_width)
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn max_word_width(&self, text: &str, px: f32) -> f32 {
|
||||
text.split(' ')
|
||||
.map(|word| word.chars()
|
||||
.map(|c| self.font.metrics(c, px).advance_width)
|
||||
.sum::<f32>())
|
||||
.fold(0.0, f32::max)
|
||||
}
|
||||
fn measure_and_max_word(&self, text: &str, px: f32) -> (f32, f32) {
|
||||
let mut total: f32 = 0.0;
|
||||
let mut max_word: f32 = 0.0;
|
||||
|
||||
for word in text.split(' ') {
|
||||
let word_width: f32 = word.chars()
|
||||
.map(|c| self.font.metrics(c, px).advance_width)
|
||||
.sum();
|
||||
total += word_width + self.font.metrics(' ', px).advance_width;
|
||||
max_word = max_word.max(word_width);
|
||||
}
|
||||
|
||||
if text.ends_with(' ') {
|
||||
// trailing space is valid
|
||||
} else if total > 0.0 {
|
||||
total -= self.font.metrics(' ', px).advance_width;
|
||||
}
|
||||
|
||||
(total, max_word)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn gtfont(_py: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
_py.add_class::<FontMeasurer>()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -36,6 +36,7 @@ write_to = "greater_tables/_version.py"
|
||||
version_scheme = "post-release"
|
||||
local_scheme = "node-and-date" # "no-local-version"
|
||||
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["greater_tables", "greater_tables.data"]
|
||||
exclude = ["img", "tests", "docs"]
|
||||
@@ -48,6 +49,8 @@ version = { attr = "greater_tables.__version__" }
|
||||
|
||||
[project.urls]
|
||||
"Source Code" = "https://github.com/mynl/greater_tables_project"
|
||||
"Documentation" = "https://greater-tables-project.readthedocs.io/en/latest/"
|
||||
"Changelog" = "https://greater-tables-project.readthedocs.io/en/latest/versions.html"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
|
||||
Reference in New Issue
Block a user