@@ -13,6 +13,7 @@ import { adjustOrthographicFrustum, applyCameraSnapshot, captureCameraSnapshot }
1313const UPDATE_CAMERA_TOOLTIP = 'Update this view to match the current camera' ;
1414const PMI_EXPORT_CAPTURE_WIDTH_PX = 2400 ;
1515const PMI_EXPORT_CAPTURE_HEIGHT_PX = 1800 ;
16+ const DEFAULT_PMI_VIEW_TEXT_SIZE_PT = 12 ;
1617const SVG_NS = 'http://www.w3.org/2000/svg' ;
1718
1819export class PMIViewsWidget {
@@ -113,6 +114,18 @@ export class PMIViewsWidget {
113114 } ;
114115 }
115116
117+ _normalizeViewTextSizePt ( value , fallback = DEFAULT_PMI_VIEW_TEXT_SIZE_PT ) {
118+ const numeric = Number ( value ) ;
119+ if ( Number . isFinite ( numeric ) && numeric > 0 ) {
120+ return Math . max ( 1 , Math . min ( 288 , numeric ) ) ;
121+ }
122+ return fallback ;
123+ }
124+
125+ _getViewTextSizePt ( view , fallback = DEFAULT_PMI_VIEW_TEXT_SIZE_PT ) {
126+ return this . _normalizeViewTextSizePt ( ( view ?. viewSettings || view ?. settings ) ?. pmiTextSizePt , fallback ) ;
127+ }
128+
116129 _updateViewportSensitiveRendering ( width , height , viewerContext = this . viewer ) {
117130 const safeWidth = Math . max ( 1 , Number ( width ) || 1 ) ;
118131 const safeHeight = Math . max ( 1 , Number ( height ) || 1 ) ;
@@ -612,17 +625,44 @@ export class PMIViewsWidget {
612625 return group ;
613626 }
614627
628+ _resolveSheetPlacementPpi ( renderContext = null ) {
629+ const viewportWidth = Number ( renderContext ?. viewport ?. width ) ;
630+ const viewportHeight = Number ( renderContext ?. viewport ?. height ) ;
631+ const frameWidthIn = Number ( renderContext ?. targetFrameWidthIn ) ;
632+ const frameHeightIn = Number ( renderContext ?. targetFrameHeightIn ) ;
633+ if ( ! ( viewportWidth > 0 ) || ! ( viewportHeight > 0 ) || ! ( frameWidthIn > 0 ) || ! ( frameHeightIn > 0 ) ) {
634+ return null ;
635+ }
636+ return Math . max ( viewportWidth / frameWidthIn , viewportHeight / frameHeightIn ) ;
637+ }
638+
639+ _resolveLabelFontSizePx ( renderContext = null ) {
640+ const ppi = this . _resolveSheetPlacementPpi ( renderContext ) ;
641+ if ( ! ( ppi > 0 ) ) return null ;
642+ const textSizePt = this . _getViewTextSizePt ( renderContext ?. view || null ) ;
643+ return ( textSizePt / 72 ) * ppi ;
644+ }
645+
646+ _getLabelLayoutMetrics ( width , cssWidth = null , renderContext = null ) {
647+ const safeCssWidth = Math . max ( 1 , cssWidth || width ) ;
648+ const dpr = Math . max ( 1 , width / safeCssWidth ) ;
649+ const fontSize = this . _resolveLabelFontSizePx ( renderContext ) || ( 14 * dpr ) ;
650+ return {
651+ dpr,
652+ paddingX : Math . max ( 4 * dpr , fontSize * ( 8 / 14 ) ) ,
653+ paddingY : Math . max ( 3 * dpr , fontSize * ( 6 / 14 ) ) ,
654+ lineHeight : Math . max ( fontSize , fontSize * ( 18 / 14 ) ) ,
655+ radius : Math . max ( 4 * dpr , fontSize * ( 8 / 14 ) ) ,
656+ fontSize,
657+ } ;
658+ }
659+
615660 _buildLabelLayout ( labels , width , height , cssWidth = null , renderContext = null , { svgCentered = false } = { } ) {
616661 const camera = renderContext ?. viewer ?. camera || this . viewer ?. camera ;
617662 const isMonochrome = this . _isMonochromeExport ( renderContext ) ;
618- const safeCssWidth = Math . max ( 1 , cssWidth || width ) ;
619- const dpr = Math . max ( 1 , width / safeCssWidth ) ;
620- const paddingX = 8 * dpr ;
621- const paddingY = 6 * dpr ;
622- const lineHeight = 18 * dpr ;
623- const radius = 8 * dpr ;
663+ const { dpr, paddingX, paddingY, lineHeight, radius, fontSize } =
664+ this . _getLabelLayoutMetrics ( width , cssWidth , renderContext ) ;
624665 const fontFamily = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' ;
625- const fontSize = 14 * dpr ;
626666
627667 const layout = [ ] ;
628668 labels . forEach ( ( label ) => {
@@ -955,6 +995,8 @@ export class PMIViewsWidget {
955995 viewport,
956996 renderMode,
957997 showCenterLines,
998+ targetFrameWidthIn : Number ( options ?. targetFrameWidthIn ) > 0 ? Number ( options . targetFrameWidthIn ) : null ,
999+ targetFrameHeightIn : Number ( options ?. targetFrameHeightIn ) > 0 ? Number ( options . targetFrameHeightIn ) : null ,
9581000 dispose : ( ) => {
9591001 try {
9601002 if ( camera . parent === scene ) scene . remove ( camera ) ;
@@ -1413,13 +1455,25 @@ export class PMIViewsWidget {
14131455 }
14141456 }
14151457
1416- async captureViewImageDataUrl ( view , viewIndex , { hideViewCube = true , renderMode = 'shaded' , showCenterLines = false } = { } ) {
1458+ async captureViewImageDataUrl ( view , viewIndex , {
1459+ hideViewCube = true ,
1460+ renderMode = 'shaded' ,
1461+ showCenterLines = false ,
1462+ targetFrameWidthIn = null ,
1463+ targetFrameHeightIn = null ,
1464+ } = { } ) {
14171465 const viewer = this . viewer ;
14181466 if ( ! viewer ) throw new Error ( 'Viewer is not ready to export images' ) ;
14191467
14201468 const captureViewport = this . _getCaptureViewportMetrics ( view ) ;
14211469 const originalWireframe = this . _detectWireframe ( viewer . scene ) ;
1422- const renderContext = this . _createExportRenderContext ( captureViewport , view , { renderMode, showCenterLines } ) ;
1470+ const renderContext = this . _createExportRenderContext ( captureViewport , view , {
1471+ renderMode,
1472+ showCenterLines,
1473+ targetFrameWidthIn,
1474+ targetFrameHeightIn,
1475+ } ) ;
1476+ renderContext . view = view || null ;
14231477
14241478 let dataUrl = null ;
14251479 const runCapture = async ( ) => {
@@ -1486,6 +1540,8 @@ export class PMIViewsWidget {
14861540 .pmi-row-menu .pmi-btn { width: 100%; justify-content: flex-start; }
14871541 .pmi-row-menu .pmi-btn.danger { justify-content: center; }
14881542 .pmi-row-menu-wireframe { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #e5e7eb; }
1543+ .pmi-row-menu-setting { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: #e5e7eb; }
1544+ .pmi-row-menu-setting .pmi-input { min-width: 0; }
14891545 .pmi-row-menu hr { border: none; border-top: 1px solid #1f2937; margin: 4px 0; }
14901546 ` ;
14911547 document . head . appendChild ( style ) ;
@@ -1670,6 +1726,27 @@ export class PMIViewsWidget {
16701726 wireframeLabel . appendChild ( wireframeText ) ;
16711727 menu . appendChild ( wireframeLabel ) ;
16721728
1729+ const textSizeWrap = document . createElement ( 'label' ) ;
1730+ textSizeWrap . className = 'pmi-row-menu-setting' ;
1731+ const textSizeText = document . createElement ( 'span' ) ;
1732+ textSizeText . textContent = 'Text size (pt)' ;
1733+ const textSizeInput = document . createElement ( 'input' ) ;
1734+ textSizeInput . type = 'number' ;
1735+ textSizeInput . min = '1' ;
1736+ textSizeInput . max = '288' ;
1737+ textSizeInput . step = '0.5' ;
1738+ textSizeInput . className = 'pmi-input' ;
1739+ textSizeInput . value = String ( this . _getViewTextSizePt ( v ) ) ;
1740+ textSizeInput . title = 'PMI label size on the sheet in points' ;
1741+ textSizeInput . addEventListener ( 'click' , ( evt ) => evt . stopPropagation ( ) ) ;
1742+ textSizeInput . addEventListener ( 'change' , ( evt ) => {
1743+ evt . stopPropagation ( ) ;
1744+ this . _setViewTextSizePt ( idx , textSizeInput . value ) ;
1745+ } ) ;
1746+ textSizeWrap . appendChild ( textSizeText ) ;
1747+ textSizeWrap . appendChild ( textSizeInput ) ;
1748+ menu . appendChild ( textSizeWrap ) ;
1749+
16731750 menuBtn . addEventListener ( 'click' , ( evt ) => {
16741751 evt . stopPropagation ( ) ;
16751752 this . _toggleRowMenu ( menu , menuBtn ) ;
@@ -2174,14 +2251,9 @@ export class PMIViewsWidget {
21742251 if ( ! baseImage ) throw new Error ( 'Base image missing for SVG composition' ) ;
21752252 const camera = renderContext ?. viewer ?. camera || this . viewer ?. camera ;
21762253 const isMonochrome = this . _isMonochromeExport ( renderContext ) ;
2177- const safeCssWidth = Math . max ( 1 , cssWidth || width ) ;
2178- const dpr = Math . max ( 1 , width / safeCssWidth ) ;
2179- const paddingX = 8 * dpr ;
2180- const paddingY = 6 * dpr ;
2181- const lineHeight = 18 * dpr ;
2182- const radius = 8 * dpr ;
2254+ const { paddingX, paddingY, lineHeight, radius, fontSize } =
2255+ this . _getLabelLayoutMetrics ( width , cssWidth , renderContext ) ;
21832256 const fontFamily = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' ;
2184- const fontSize = 14 * dpr ;
21852257
21862258 const layout = [ ] ;
21872259 labels . forEach ( ( label ) => {
@@ -2526,6 +2598,35 @@ export class PMIViewsWidget {
25262598 }
25272599 }
25282600
2601+ _setViewTextSizePt ( index , value ) {
2602+ if ( this . _readOnly ) return ;
2603+ const nextTextSizePt = this . _normalizeViewTextSizePt ( value ) ;
2604+ const applyValue = ( entry ) => {
2605+ if ( ! entry || typeof entry !== 'object' ) return entry ;
2606+ if ( ! entry . viewSettings || typeof entry . viewSettings !== 'object' ) {
2607+ entry . viewSettings = { } ;
2608+ }
2609+ entry . viewSettings . pmiTextSizePt = nextTextSizePt ;
2610+ return entry ;
2611+ } ;
2612+
2613+ let updated = false ;
2614+ const manager = this . viewer ?. partHistory ?. pmiViewsManager ;
2615+ if ( manager && typeof manager . updateView === 'function' ) {
2616+ const result = manager . updateView ( index , ( entry ) => applyValue ( entry ) ) ;
2617+ updated = Boolean ( result ) ;
2618+ } else if ( Array . isArray ( this . views ) && this . views [ index ] ) {
2619+ applyValue ( this . views [ index ] ) ;
2620+ updated = true ;
2621+ this . refreshFromHistory ( ) ;
2622+ }
2623+
2624+ if ( ! updated ) {
2625+ this . refreshFromHistory ( ) ;
2626+ this . _renderList ( ) ;
2627+ }
2628+ }
2629+
25292630 _updateViewCamera ( index ) {
25302631 if ( this . _readOnly ) return ;
25312632 try {
0 commit comments