forked from 1b-agent/opencadstudio-upstream
feat(pdf): searchable text layer with per-style subset font embedding
Real BT/Tj text objects ride alongside the vector outlines (Tr 3, zero visual change): runs preserved at layout time, TTF styles embedded as allsorts subsets with ToUnicode, width-matched Tm, clip-culled. WireModel literals moved to ..Default::default() tails.
This commit is contained in:
1 parent
443b935a3f
commit
02c470aac7
44 files changed
+1534
-127
No files matched your search
@@ -4051,7 +4051,9 @@ fn tessellate_dimension_inner(
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
if !ext2.is_empty() {
|
||||
wires.push(WireModel {
|
||||
@@ -4088,7 +4090,9 @@ fn tessellate_dimension_inner(
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
} else {
|
||||
wires.push(WireModel {
|
||||
@@ -4125,7 +4129,9 @@ fn tessellate_dimension_inner(
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4168,7 +4174,9 @@ fn tessellate_dimension_inner(
|
||||
// — not a crash. Follow-up: double-single-split via points_to_ds to
|
||||
// match emit_wire's paired fill path.
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
// Arrowheads and ticks: the dimension line's colour and weight, never
|
||||
// its dashes.
|
||||
if !geom.arrow_lines.is_empty() {
|
||||
@@ -4224,7 +4232,9 @@ fn tessellate_dimension_inner(
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
// DIMTFILL: 0=none, 1=drawing background (mask), 2=DIMTFILLCLR.
|
||||
@@ -4279,7 +4289,9 @@ fn tessellate_dimension_inner(
|
||||
// (sub-metre error at UTM scale) — not a crash. Follow-up:
|
||||
// double-single-split via points_to_ds to match emit_wire.
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4326,7 +4338,9 @@ fn tessellate_dimension_inner(
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5855,6 +5869,15 @@ fn map_wire_ocs_to_wcs(wire: &mut WireModel, normal: Vector3) {
|
||||
&wire.text_verts,
|
||||
map,
|
||||
);
|
||||
// OCS→WCS is rigid: unit height scale, no in-plane rotation change.
|
||||
wire.map_searchable_runs(
|
||||
&|p| {
|
||||
let mapped = map(p[0], p[1], p[2]);
|
||||
[mapped.0, mapped.1, mapped.2]
|
||||
},
|
||||
1.0,
|
||||
0.0,
|
||||
);
|
||||
for (point, _) in &mut wire.snap_pts {
|
||||
let mapped = map(point.x, point.y, point.z);
|
||||
*point = DVec3::new(mapped.0, mapped.1, mapped.2);
|
||||
|
||||
@@ -705,6 +705,7 @@ impl LeaderTess for Leader {
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -887,6 +888,7 @@ impl LeaderTess for Leader {
|
||||
plinegen: true,
|
||||
fill_tris,
|
||||
fill_tris_low,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
-15
@@ -2106,7 +2106,9 @@ impl MultiLeaderTess for MultiLeader {
|
||||
// error at UTM scale) — not a crash. Follow-up: double-single-split
|
||||
// via points_to_ds to match emit_wire's paired fill path.
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let host = codec::EntityType::MultiLeader(Box::new(ml.clone()));
|
||||
for block_use in crate::scene::render_graph::entity_render_block_uses(document, &host, 1.0)
|
||||
@@ -2299,6 +2301,8 @@ impl MultiLeaderTess for MultiLeader {
|
||||
// cluster-position data.
|
||||
let mut deco_pts: Vec<[f32; 3]> = Vec::new();
|
||||
let mut deco_fill: Vec<[f32; 3]> = Vec::new();
|
||||
let mut searchable: Vec<crate::scene::model::wire_model::SearchableTextRun> =
|
||||
Vec::new();
|
||||
if let Ok(mut atlas) = crate::scene::text::sdf_atlas::text_atlas().lock() {
|
||||
for ts in &layout.strokes {
|
||||
let is_shaped = ts
|
||||
@@ -2331,17 +2335,18 @@ impl MultiLeaderTess for MultiLeader {
|
||||
continue;
|
||||
}
|
||||
let run = ts.run.as_ref().unwrap();
|
||||
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||
&mut atlas,
|
||||
run.height,
|
||||
run.rotation,
|
||||
run.width_factor,
|
||||
run.oblique,
|
||||
run.tracking,
|
||||
&run.font,
|
||||
run.bold,
|
||||
&run.text,
|
||||
);
|
||||
let (quads, pen_adv) =
|
||||
crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||
&mut atlas,
|
||||
run.height,
|
||||
run.rotation,
|
||||
run.width_factor,
|
||||
run.oblique,
|
||||
run.tracking,
|
||||
&run.font,
|
||||
run.bold,
|
||||
&run.text,
|
||||
);
|
||||
// Inline `\C` / `\c` colour wins over the entity text
|
||||
// colour, matching the top-level Text arm.
|
||||
let gcolor = ts
|
||||
@@ -2356,6 +2361,24 @@ impl MultiLeaderTess for MultiLeader {
|
||||
gcolor,
|
||||
0.0,
|
||||
);
|
||||
{
|
||||
use crate::scene::model::wire_model::{
|
||||
clean_searchable_text, SearchableTextRun,
|
||||
};
|
||||
let visible = clean_searchable_text(&run.text);
|
||||
if !visible.is_empty() {
|
||||
searchable.push(SearchableTextRun {
|
||||
text: visible,
|
||||
origin: [ts.origin[0], ts.origin[1], z as f64],
|
||||
height: run.height,
|
||||
rotation: run.rotation,
|
||||
color: gcolor,
|
||||
bold: run.bold,
|
||||
font: run.font.clone(),
|
||||
adv_width: pen_adv,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !sdf_verts.is_empty() || !deco_pts.is_empty() || !deco_fill.is_empty() {
|
||||
@@ -2400,6 +2423,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: sdf_verts,
|
||||
searchable_text: searchable,
|
||||
name: name.clone(),
|
||||
points: deco_pts,
|
||||
points_low: Vec::new(),
|
||||
@@ -2492,7 +2516,9 @@ impl MultiLeaderTess for MultiLeader {
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2583,7 +2609,9 @@ impl MultiLeaderTess for MultiLeader {
|
||||
plinegen: true,
|
||||
fill_tris,
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
// Text frame — closed rectangle, matches text color.
|
||||
@@ -2629,7 +2657,9 @@ impl MultiLeaderTess for MultiLeader {
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-12
@@ -1488,6 +1488,7 @@ pub fn tessellate_table(
|
||||
// SDF cell text: glyph quads (per-vertex coloured) collected across all
|
||||
// cells; emitted as one text-carrying wire at the end.
|
||||
let mut text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
|
||||
let mut searchable: Vec<crate::scene::model::wire_model::SearchableTextRun> = Vec::new();
|
||||
let mut borders: HashMap<([u8; 4], u32), ([f32; 4], f32, Vec<[f32; 3]>)> = HashMap::default();
|
||||
let mut emitted: rustc_hash::FxHashSet<(i32, i32, i32, i32, i32, i32)> =
|
||||
rustc_hash::FxHashSet::default();
|
||||
@@ -2016,17 +2017,18 @@ pub fn tessellate_table(
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||
&mut atlas,
|
||||
run.height,
|
||||
run.rotation,
|
||||
run.width_factor,
|
||||
run.oblique,
|
||||
run.tracking,
|
||||
&run.font,
|
||||
run.bold,
|
||||
&run.text,
|
||||
);
|
||||
let (quads, pen_adv) =
|
||||
crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||
&mut atlas,
|
||||
run.height,
|
||||
run.rotation,
|
||||
run.width_factor,
|
||||
run.oblique,
|
||||
run.tracking,
|
||||
&run.font,
|
||||
run.bold,
|
||||
&run.text,
|
||||
);
|
||||
crate::scene::pipeline::text_gpu::push_glyph_vertices(
|
||||
&mut text_verts,
|
||||
&quads,
|
||||
@@ -2035,6 +2037,24 @@ pub fn tessellate_table(
|
||||
tcol,
|
||||
0.0,
|
||||
);
|
||||
{
|
||||
use crate::scene::model::wire_model::{
|
||||
clean_searchable_text, SearchableTextRun,
|
||||
};
|
||||
let visible = clean_searchable_text(&run.text);
|
||||
if !visible.is_empty() {
|
||||
searchable.push(SearchableTextRun {
|
||||
text: visible,
|
||||
origin: [stroke.origin[0], stroke.origin[1], to.z as f64],
|
||||
height: run.height,
|
||||
rotation: run.rotation,
|
||||
color: tcol,
|
||||
bold: run.bold,
|
||||
font: run.font.clone(),
|
||||
adv_width: pen_adv,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2084,7 +2104,9 @@ pub fn tessellate_table(
|
||||
// (sub-metre error at UTM scale) — not a crash. Follow-up:
|
||||
// double-single-split via points_to_ds to match emit_wire.
|
||||
fill_tris_low: Vec::new(),
|
||||
}
|
||||
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
let mut out: Vec<WireModel> = Vec::new();
|
||||
@@ -2115,6 +2137,7 @@ pub fn tessellate_table(
|
||||
let mut w = mk(entity_color, vec![], vec![], line_weight_px);
|
||||
w.aabb = [nx as f32, ny as f32, xx as f32, xy as f32];
|
||||
w.text_verts = text_verts;
|
||||
w.searchable_text = searchable;
|
||||
out.push(w);
|
||||
}
|
||||
out
|
||||
|
||||
Reference in new issue
Block a user