three-text 0.2.17 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +100 -84
- package/dist/index.js +100 -84
- package/dist/index.min.cjs +259 -251
- package/dist/index.min.js +319 -311
- package/dist/index.umd.js +100 -84
- package/dist/index.umd.min.js +295 -287
- package/dist/types/core/geometry/Extruder.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* three-text v0.2.
|
|
2
|
+
* three-text v0.2.18
|
|
3
3
|
* Copyright (C) 2025 Countertype LLC
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
@@ -2731,7 +2731,9 @@
|
|
|
2731
2731
|
tessContours = originalContours;
|
|
2732
2732
|
}
|
|
2733
2733
|
let extrusionContours = needsExtrusionContours
|
|
2734
|
-
?
|
|
2734
|
+
? needsWindingReversal
|
|
2735
|
+
? tessContours
|
|
2736
|
+
: originalContours ?? this.pathsToContours(paths)
|
|
2735
2737
|
: [];
|
|
2736
2738
|
if (removeOverlaps) {
|
|
2737
2739
|
logger.log('Two-pass: boundary extraction then triangulation');
|
|
@@ -2753,24 +2755,6 @@
|
|
|
2753
2755
|
}
|
|
2754
2756
|
else {
|
|
2755
2757
|
logger.log(`Single-pass triangulation for ${isCFF ? 'CFF' : 'TTF'}`);
|
|
2756
|
-
// TTF contours may have inconsistent winding; check if we need normalization
|
|
2757
|
-
if (needsExtrusionContours && !isCFF) {
|
|
2758
|
-
const needsNormalization = this.needsWindingNormalization(extrusionContours);
|
|
2759
|
-
if (needsNormalization) {
|
|
2760
|
-
logger.log('Complex topology detected, running boundary pass for winding normalization');
|
|
2761
|
-
perfLogger.start('Tessellator.windingNormalization', {
|
|
2762
|
-
contourCount: extrusionContours.length
|
|
2763
|
-
});
|
|
2764
|
-
const boundaryResult = this.performTessellation(extrusionContours, 'boundary');
|
|
2765
|
-
perfLogger.end('Tessellator.windingNormalization');
|
|
2766
|
-
if (boundaryResult) {
|
|
2767
|
-
extrusionContours = this.boundaryToContours(boundaryResult);
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
else {
|
|
2771
|
-
logger.log('Simple topology, skipping winding normalization');
|
|
2772
|
-
}
|
|
2773
|
-
}
|
|
2774
2758
|
}
|
|
2775
2759
|
perfLogger.start('Tessellator.triangulationPass', {
|
|
2776
2760
|
contourCount: tessContours.length
|
|
@@ -2952,28 +2936,58 @@
|
|
|
2952
2936
|
|
|
2953
2937
|
class Extruder {
|
|
2954
2938
|
constructor() { }
|
|
2939
|
+
packEdge(a, b) {
|
|
2940
|
+
const lo = a < b ? a : b;
|
|
2941
|
+
const hi = a < b ? b : a;
|
|
2942
|
+
return lo * 0x100000000 + hi;
|
|
2943
|
+
}
|
|
2955
2944
|
extrude(geometry, depth = 0, unitsPerEm) {
|
|
2956
2945
|
const points = geometry.triangles.vertices;
|
|
2957
2946
|
const triangleIndices = geometry.triangles.indices;
|
|
2958
2947
|
const numPoints = points.length / 2;
|
|
2959
|
-
// Count side
|
|
2960
|
-
let
|
|
2948
|
+
// Count boundary edges for side walls (4 vertices + 6 indices per edge)
|
|
2949
|
+
let boundaryEdges = [];
|
|
2961
2950
|
if (depth !== 0) {
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2951
|
+
const counts = new Map();
|
|
2952
|
+
const oriented = new Map();
|
|
2953
|
+
for (let i = 0; i < triangleIndices.length; i += 3) {
|
|
2954
|
+
const a = triangleIndices[i];
|
|
2955
|
+
const b = triangleIndices[i + 1];
|
|
2956
|
+
const c = triangleIndices[i + 2];
|
|
2957
|
+
const k0 = this.packEdge(a, b);
|
|
2958
|
+
const n0 = (counts.get(k0) ?? 0) + 1;
|
|
2959
|
+
counts.set(k0, n0);
|
|
2960
|
+
if (n0 === 1)
|
|
2961
|
+
oriented.set(k0, [a, b]);
|
|
2962
|
+
const k1 = this.packEdge(b, c);
|
|
2963
|
+
const n1 = (counts.get(k1) ?? 0) + 1;
|
|
2964
|
+
counts.set(k1, n1);
|
|
2965
|
+
if (n1 === 1)
|
|
2966
|
+
oriented.set(k1, [b, c]);
|
|
2967
|
+
const k2 = this.packEdge(c, a);
|
|
2968
|
+
const n2 = (counts.get(k2) ?? 0) + 1;
|
|
2969
|
+
counts.set(k2, n2);
|
|
2970
|
+
if (n2 === 1)
|
|
2971
|
+
oriented.set(k2, [c, a]);
|
|
2972
|
+
}
|
|
2973
|
+
boundaryEdges = [];
|
|
2974
|
+
for (const [key, count] of counts) {
|
|
2975
|
+
if (count !== 1)
|
|
2976
|
+
continue;
|
|
2977
|
+
const edge = oriented.get(key);
|
|
2978
|
+
if (edge)
|
|
2979
|
+
boundaryEdges.push(edge);
|
|
2967
2980
|
}
|
|
2968
2981
|
}
|
|
2969
|
-
const
|
|
2982
|
+
const sideEdgeCount = depth === 0 ? 0 : boundaryEdges.length;
|
|
2983
|
+
const sideVertexCount = depth === 0 ? 0 : sideEdgeCount * 4;
|
|
2970
2984
|
const baseVertexCount = depth === 0 ? numPoints : numPoints * 2;
|
|
2971
2985
|
const vertexCount = baseVertexCount + sideVertexCount;
|
|
2972
2986
|
const vertices = new Float32Array(vertexCount * 3);
|
|
2973
2987
|
const normals = new Float32Array(vertexCount * 3);
|
|
2974
2988
|
const indexCount = depth === 0
|
|
2975
2989
|
? triangleIndices.length
|
|
2976
|
-
: triangleIndices.length * 2 +
|
|
2990
|
+
: triangleIndices.length * 2 + sideEdgeCount * 6;
|
|
2977
2991
|
const indices = new Uint32Array(indexCount);
|
|
2978
2992
|
if (depth === 0) {
|
|
2979
2993
|
// Single-sided flat geometry at z=0
|
|
@@ -3029,60 +3043,62 @@
|
|
|
3029
3043
|
// Side walls
|
|
3030
3044
|
let nextVertex = numPoints * 2;
|
|
3031
3045
|
let idxPos = triangleIndices.length * 2;
|
|
3032
|
-
for (
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3046
|
+
for (let e = 0; e < boundaryEdges.length; e++) {
|
|
3047
|
+
const [u, v] = boundaryEdges[e];
|
|
3048
|
+
const u2 = u * 2;
|
|
3049
|
+
const v2 = v * 2;
|
|
3050
|
+
const p0x = points[u2];
|
|
3051
|
+
const p0y = points[u2 + 1];
|
|
3052
|
+
const p1x = points[v2];
|
|
3053
|
+
const p1y = points[v2 + 1];
|
|
3054
|
+
// Perpendicular normal for this wall segment
|
|
3055
|
+
// Uses the edge direction from the cap triangulation so winding does not depend on contour direction
|
|
3056
|
+
const ex = p1x - p0x;
|
|
3057
|
+
const ey = p1y - p0y;
|
|
3058
|
+
const lenSq = ex * ex + ey * ey;
|
|
3059
|
+
let nx = 0;
|
|
3060
|
+
let ny = 0;
|
|
3061
|
+
if (lenSq > 0) {
|
|
3062
|
+
const invLen = 1 / Math.sqrt(lenSq);
|
|
3063
|
+
nx = ey * invLen;
|
|
3064
|
+
ny = -ex * invLen;
|
|
3065
|
+
}
|
|
3066
|
+
const baseVertex = nextVertex;
|
|
3067
|
+
const base = baseVertex * 3;
|
|
3068
|
+
// Wall quad: front edge at z=0, back edge at z=depth
|
|
3069
|
+
vertices[base] = p0x;
|
|
3070
|
+
vertices[base + 1] = p0y;
|
|
3071
|
+
vertices[base + 2] = 0;
|
|
3072
|
+
vertices[base + 3] = p1x;
|
|
3073
|
+
vertices[base + 4] = p1y;
|
|
3074
|
+
vertices[base + 5] = 0;
|
|
3075
|
+
vertices[base + 6] = p0x;
|
|
3076
|
+
vertices[base + 7] = p0y;
|
|
3077
|
+
vertices[base + 8] = backZ;
|
|
3078
|
+
vertices[base + 9] = p1x;
|
|
3079
|
+
vertices[base + 10] = p1y;
|
|
3080
|
+
vertices[base + 11] = backZ;
|
|
3081
|
+
// Wall normals point perpendicular to edge
|
|
3082
|
+
normals[base] = nx;
|
|
3083
|
+
normals[base + 1] = ny;
|
|
3084
|
+
normals[base + 2] = 0;
|
|
3085
|
+
normals[base + 3] = nx;
|
|
3086
|
+
normals[base + 4] = ny;
|
|
3087
|
+
normals[base + 5] = 0;
|
|
3088
|
+
normals[base + 6] = nx;
|
|
3089
|
+
normals[base + 7] = ny;
|
|
3090
|
+
normals[base + 8] = 0;
|
|
3091
|
+
normals[base + 9] = nx;
|
|
3092
|
+
normals[base + 10] = ny;
|
|
3093
|
+
normals[base + 11] = 0;
|
|
3094
|
+
// Two triangles per wall segment
|
|
3095
|
+
indices[idxPos++] = baseVertex;
|
|
3096
|
+
indices[idxPos++] = baseVertex + 1;
|
|
3097
|
+
indices[idxPos++] = baseVertex + 2;
|
|
3098
|
+
indices[idxPos++] = baseVertex + 1;
|
|
3099
|
+
indices[idxPos++] = baseVertex + 3;
|
|
3100
|
+
indices[idxPos++] = baseVertex + 2;
|
|
3101
|
+
nextVertex += 4;
|
|
3086
3102
|
}
|
|
3087
3103
|
return { vertices, normals, indices };
|
|
3088
3104
|
}
|
|
@@ -4181,7 +4197,7 @@
|
|
|
4181
4197
|
// Use glyph-level caching when separateGlyphs is set or when cluster contains colored text
|
|
4182
4198
|
const forceSeparate = separateGlyphs || clusterHasColoredGlyphs;
|
|
4183
4199
|
// Iterate over the geometric groups identified by BoundaryClusterer
|
|
4184
|
-
// logical groups (words) split into geometric sub-groups
|
|
4200
|
+
// logical groups (words) split into geometric sub-groups
|
|
4185
4201
|
for (const groupIndices of boundaryGroups) {
|
|
4186
4202
|
const isOverlappingGroup = groupIndices.length > 1;
|
|
4187
4203
|
const shouldCluster = isOverlappingGroup && !forceSeparate;
|