feat(fonts): switch the text engine from CXF to LibreCAD LFF fonts
Replace the small QCAD CXF stroke-font set with the full LibreCAD LFF font library (46 fonts) and a new LFF parser/engine in src/scene/lff.rs. The parser reads the `[hex] char` glyph blocks, tessellates `,A<bulge>` arcs, and resolves `C<hex>` glyph references. Per LibreCAD's RS_Polyline convention a vertex's bulge curves the segment ending at that vertex — getting this wrong turned round letters like O into sideways capsules. Font name resolution mirrors LibreCAD: match by file stem, map common AutoCAD/DXF SHX names (txt, isocp, complex, italic, gothic*, gost, …) to the nearest LFF family, and fall back to `standard`. A missing glyph in the chosen family falls back to the broad `unicode` font so accented/Cyrillic/Greek text still renders. Cap height stays 9 units, so the text scale is unchanged. All text consumers move from cxf:: to lff::; complex linetypes still render embedded text but no longer draw embedded shapes (LibreCAD ships no shape file). The CXF module and every *.cxf asset are removed, and the MText / text style font pickers list the LFF families. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
32136dd9dd
commit
c1b33677e2
98 changed files with 886126 additions and 47228 deletions
|
|
@ -15,7 +15,7 @@ use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckC
|
|||
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::wire_model::SnapHint;
|
||||
use crate::scene::{cxf, transform};
|
||||
use crate::scene::{lff, transform};
|
||||
|
||||
// ── Shared helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ fn build_attr_truck(input: AttrTextInputs<'_>, document: &acadrust::CadDocument)
|
|||
anchor_f64[0] - (anchor_local_x as f64 * cos_r - local_y_for_line as f64 * sin_r),
|
||||
anchor_f64[1] - (anchor_local_x as f64 * sin_r + local_y_for_line as f64 * cos_r),
|
||||
];
|
||||
let strokes = cxf::tessellate_text_ex(
|
||||
let strokes = lff::tessellate_text_ex(
|
||||
[0.0, 0.0],
|
||||
input.height as f32,
|
||||
rotation,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::entities::text_support::{
|
|||
};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
|
||||
use crate::scene::cxf;
|
||||
use crate::scene::lff;
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::wire_model::SnapHint;
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
|
|||
anchor_f64[1] - (anchor_local_x as f64 * sin_r + anchor_local_y as f64 * cos_r),
|
||||
];
|
||||
// Strokes are in glyph-local space (origin = [0,0]).
|
||||
let strokes = cxf::tessellate_text_ex(
|
||||
let strokes = lff::tessellate_text_ex(
|
||||
[0.0, 0.0],
|
||||
t.height as f32,
|
||||
rotation,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use acadrust::types::aci_table::aci_to_rgb;
|
|||
use acadrust::{CadDocument, EntityType};
|
||||
|
||||
use crate::scene::acad_to_truck::TextStroke;
|
||||
use crate::scene::cxf;
|
||||
use crate::scene::lff;
|
||||
|
||||
pub struct ResolvedTextStyle {
|
||||
pub font_name: String,
|
||||
|
|
@ -65,7 +65,7 @@ pub fn text_local_bounds(
|
|||
return None;
|
||||
}
|
||||
|
||||
let font = cxf::get_font(font_name);
|
||||
let font = lff::get_font(font_name);
|
||||
let scale = height / 9.0;
|
||||
let wf = width_factor.abs().clamp(0.01, 100.0);
|
||||
let ob = oblique_angle.tan();
|
||||
|
|
@ -846,7 +846,7 @@ pub fn measure_word(
|
|||
) -> f32 {
|
||||
let scale = run_scale(state, entity_h, base_wf);
|
||||
let font_name = resolve_font(state, base_font);
|
||||
let font = cxf::get_font(font_name);
|
||||
let font = lff::get_font(font_name);
|
||||
let mut w = 0.0_f32;
|
||||
for ch in text.chars() {
|
||||
w += match font.glyph(ch) {
|
||||
|
|
@ -860,7 +860,7 @@ pub fn measure_word(
|
|||
pub fn measure_space(state: &RunState, entity_h: f32, base_wf: f32, base_font: &str) -> f32 {
|
||||
let scale = run_scale(state, entity_h, base_wf);
|
||||
let font_name = resolve_font(state, base_font);
|
||||
cxf::get_font(font_name).word_spacing * scale
|
||||
lff::get_font(font_name).word_spacing * scale
|
||||
}
|
||||
|
||||
pub fn atom_width(atom: &LayoutAtom, entity_h: f32, base_wf: f32, base_font: &str) -> f32 {
|
||||
|
|
@ -991,7 +991,7 @@ pub fn resolve_inline_color(c: &InlineColor) -> Option<[f32; 3]> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Wrap a run's glyph text with MTEXT decoration markers so cxf's
|
||||
/// Wrap a run's glyph text with MTEXT decoration markers so lff's
|
||||
/// `tessellate_text_run` emits the underline / overline / strikethrough
|
||||
/// strokes for us — keeps decoration geometry in one place rather than
|
||||
/// duplicating the y-position constants.
|
||||
|
|
@ -1115,7 +1115,7 @@ pub struct MTextLayout {
|
|||
/// text block.
|
||||
pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
||||
let base_font_name = opts.style.font_name.clone();
|
||||
let base_font = cxf::get_font(&base_font_name);
|
||||
let base_font = lff::get_font(&base_font_name);
|
||||
let base_wf_abs = opts.style.width_factor.max(0.01);
|
||||
let base_wf = if opts.style.is_backward { -base_wf_abs } else { base_wf_abs };
|
||||
let base_oblique = opts.style.oblique_angle;
|
||||
|
|
@ -1353,7 +1353,7 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
|||
ins_x + (line_base_x + world_dx) as f64,
|
||||
ins_y + (line_base_y + world_dy) as f64,
|
||||
];
|
||||
let strokes = cxf::tessellate_text_run(
|
||||
let strokes = lff::tessellate_text_run(
|
||||
[0.0, 0.0],
|
||||
run_h,
|
||||
rot,
|
||||
|
|
@ -1372,7 +1372,7 @@ pub fn layout_mtext(opts: &MTextRenderOpts) -> MTextLayout {
|
|||
// Per-character boxes, advancing exactly as
|
||||
// `measure_word` does so they track the glyphs.
|
||||
let scale = run_scale(&atom.state, entity_h, base_wf);
|
||||
let font = cxf::get_font(font_name);
|
||||
let font = lff::get_font(font_name);
|
||||
let mut cx = cursor_x;
|
||||
for ch in text.chars() {
|
||||
let adv = match font.glyph(ch) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckC
|
|||
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
use crate::scene::wire_model::SnapHint;
|
||||
use crate::scene::{cxf, transform};
|
||||
use crate::scene::{lff, transform};
|
||||
|
||||
// ── GDT text parser ───────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ fn tessellate_tolerance(tol: &Tolerance) -> Vec<Vec<[f32; 2]>> {
|
|||
let tx = cell_x + (cw - text_w) * 0.5;
|
||||
// Tessellate text in local frame then transform
|
||||
let local_strokes =
|
||||
cxf::tessellate_text_ex([0.0, 0.0], h, 0.0, 1.0, 0.0, "txt", cell);
|
||||
lff::tessellate_text_ex([0.0, 0.0], h, 0.0, 1.0, 0.0, "txt", cell);
|
||||
for polyline in local_strokes {
|
||||
let transformed: Vec<[f32; 2]> = polyline
|
||||
.into_iter()
|
||||
|
|
|
|||
Loading…
Reference in a new issue