Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

dxf-viewer

Package Overview
Dependencies
Maintainers
1
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dxf-viewer - npm Package Compare versions

Comparing version 1.0.11 to 1.0.12

CONTRIBUTORS

2

package.json
{
"name": "dxf-viewer",
"version": "1.0.11",
"version": "1.0.12",
"description": "JavaScript DXF file viewer",

@@ -5,0 +5,0 @@ "main": "src/index.js",

@@ -48,6 +48,6 @@ # DXF viewer

waste for the buffer).
* Multiline text blocks (MTEXT group).
* Text styling. Currently, text rendering is using just the specified fonts in the specified order.
DXF style and font attributes are ignored. Text glyphs are always rendered infilled.
* [Special characters](https://knowledge.autodesk.com/support/autocad/learn-explore/caas/CloudHelp/cloudhelp/2019/ENU/AutoCAD-Core/files/GUID-518E1A9D-398C-4A8A-AC32-2D85590CDBE1-htm.html) in text.
* Advanced formatting support for MTEXT (fonts, coloring, stacking).
* Line patterns - all lines are rendered in continuous style for now. I am going to use 1-D texture

@@ -65,4 +65,8 @@ generated on preparation stage, texture coordinates (which should account pattern continuity flag

* Dimensions
* Leaders
* Non-UTF-8 file encoding support. Currently, such files are displayed incorrectly. `$DWGCODEPAGE`
parameter is ignored.
* Full OCS support. Currently, it is assumed that entity extrusion direction is either +Z or -Z
(which is commonly used for features mirroring in CAD). Arbitrary directions is not properly
processed.
* Many less commonly used DXF features.

@@ -6,2 +6,3 @@ import {DynamicBuffer, NativeType} from "./DynamicBuffer"

import {RBTree} from "./RBTree"
import {MTextFormatParser} from "./MTextFormatParser";

@@ -110,5 +111,30 @@ /** Use 16-bit indices for indexed geometry. */

async _FetchFonts(dxf) {
const ProcessEntity = async (entity) => {
let ret
if (entity.type === "TEXT") {
ret = await this.textRenderer.FetchFonts(entity.text)
} else if (entity.type === "MTEXT") {
const parser = new MTextFormatParser()
parser.Parse(entity.text)
//XXX formatted MTEXT may specify some fonts explicitly, this is not yet supported
for (const text of parser.GetText()) {
if (!await this.textRenderer.FetchFonts(text)) {
ret = false
break
}
}
ret = true
} else {
throw new Error("Bad entity type")
}
if (!ret) {
this.hasMissingChars = true
}
return ret
}
for (const entity of dxf.entities) {
if (entity.type === "TEXT" || entity.type === "MTEXT") {
if (!await this.textRenderer.FetchFonts(entity.text)) {
if (!await ProcessEntity(entity)) {
/* Failing to resolve some character means that all fonts have been loaded and

@@ -120,3 +146,2 @@ * checked. No mean to check the rest strings. However until it is encountered,

*/
this.hasMissingChars = true
return

@@ -130,4 +155,3 @@ }

if (entity.type === "TEXT" || entity.type === "MTEXT") {
if (!await this.textRenderer.FetchFonts(entity.text)) {
this.hasMissingChars = true
if (!await ProcessEntity(entity)) {
return

@@ -173,2 +197,5 @@ }

break
case "MTEXT":
renderEntities = this._DecomposeMText(entity, blockCtx)
break
default:

@@ -302,4 +329,3 @@ console.log("Unhandled entity type: " + entity.type)

* @param yRadius {?number} Specify to get ellipse arc. `radius` parameter used as X radius.
* @param transform {?Matrix3} Optional transform matrix for the arc. Applied before the arc is
* positioned to the specified center.
* @param transform {?Matrix3} Optional transform matrix for the arc. Applied as last operation.
*/

@@ -353,6 +379,6 @@ _GenerateArcVertices({vertices, center, radius, startAngle = null, endAngle = null,

const v = new Vector2(radius * Math.cos(a), yRadius * Math.sin(a))
v.add(center)
if (transform) {
v.applyMatrix3(transform)
}
v.add(center)
vertices.push(v)

@@ -416,3 +442,2 @@ }

}
//XXX extrusion direction mirror
yield new Entity({

@@ -567,3 +592,3 @@ type: Entity.Type.POLYLINE,

text: entity.text,
size: entity.textHeight,
fontSize: entity.textHeight,
startPos: entity.startPoint,

@@ -579,2 +604,23 @@ endPos: entity.endPoint,

*_DecomposeMText(entity, blockCtx) {
if (!this.textRenderer.canRender) {
return
}
const layer = this._GetEntityLayer(entity, blockCtx)
const color = this._GetEntityColor(entity, blockCtx)
const parser = new MTextFormatParser()
parser.Parse(entity.text)
yield* this.textRenderer.RenderMText({
formattedText: parser.GetContent(),
fontSize: entity.height,
position: entity.position,
rotation: entity.rotation,
direction: entity.direction,
attachment: entity.attachmentPoint,
lineSpacing: entity.lineSpacing,
width: entity.width,
color, layer
})
}
/**

@@ -1079,4 +1125,4 @@ * Updates batches directly.

_GetEntityExtrusionTransform(entity) {
//XXX For now just mirror Y axis if extrusion Z is negative. Should be investigated for
// proper calculation.
//XXX For now just mirror X axis if extrusion Z is negative. No full support for arbitrary
// OCS yet.
if (!entity.hasOwnProperty("extrusionDirection")) {

@@ -1088,3 +1134,3 @@ return null

}
return new Matrix3().scale(1, -1)
return new Matrix3().scale(-1, 1)
}

@@ -1478,13 +1524,12 @@

const yScale = entity.yScale || 1
let xScale = entity.xScale || 1
const xScale = entity.xScale || 1
const rotation = -(entity.rotation || 0) * Math.PI / 180
let x = entity.position.x
let y = entity.position.y
if (entity.extrusionDirection && entity.extrusionDirection.z < 0) {
xScale = -xScale
x = -x
}
const y = entity.position.y
mInsert.scale(xScale, yScale)
mInsert.rotate(rotation)
mInsert.translate(x, y)
if (entity.extrusionDirection && entity.extrusionDirection.z < 0) {
mInsert.scale(-1, 1)
}
if (this.type !== BlockContext.Type.INSTANTIATION) {

@@ -1491,0 +1536,0 @@ return mInsert

@@ -9,2 +9,8 @@ import * as three from "three"

/** Level in "message" events. */
const MessageLevel = Object.freeze({
INFO: "info",
WARN: "warn",
ERROR: "error"
})

@@ -197,4 +203,14 @@ /** The representation class for the viewer, based on Three.js WebGL renderer. */

this.FitView(scene.bounds.minX - scene.origin.x, scene.bounds.maxX - scene.origin.x,
scene.bounds.minY - scene.origin.y, scene.bounds.maxY - scene.origin.y)
if (scene.bounds) {
this.FitView(scene.bounds.minX - scene.origin.x, scene.bounds.maxX - scene.origin.x,
scene.bounds.minY - scene.origin.y, scene.bounds.maxY - scene.origin.y)
} else {
this._Message("Empty document", MessageLevel.WARN)
}
if (this.hasMissingChars) {
this._Message("Some characters cannot be properly displayed due to missing fonts",
MessageLevel.WARN)
}
this._CreateControls()

@@ -332,2 +348,3 @@ this.Render()

* * "viewChanged"
* * "message" - Some message from the viewer. {message: string, level: string}.
*

@@ -381,2 +398,6 @@ * @param eventName {string}

_Message(message, level = MessageLevel.INFO) {
this._Emit("message", {message, level})
}
_OnPointerEvent(e) {

@@ -612,2 +633,4 @@ const canvasRect = e.target.getBoundingClientRect()

DxfViewer.MessageLevel = MessageLevel
DxfViewer.DefaultOptions = {

@@ -614,0 +637,0 @@ canvasWidth: 400,

@@ -38,3 +38,3 @@ /**

group.value = parseGroupValue(group.code, this._data[this._pointer].trim());
group.value = parseGroupValue(group.code, this._data[this._pointer]);

@@ -62,3 +62,3 @@ this._pointer++;

group.value = parseGroupValue(group.code, this._data[this._pointer + 1].trim());
group.value = parseGroupValue(group.code, this._data[this._pointer + 1]);

@@ -109,24 +109,24 @@ return group;

if(code <= 9) return value;
if(code >= 10 && code <= 59) return parseFloat(value);
if(code >= 60 && code <= 99) return parseInt(value);
if(code >= 10 && code <= 59) return parseFloat(value.trim());
if(code >= 60 && code <= 99) return parseInt(value.trim());
if(code >= 100 && code <= 109) return value;
if(code >= 110 && code <= 149) return parseFloat(value);
if(code >= 160 && code <= 179) return parseInt(value);
if(code >= 210 && code <= 239) return parseFloat(value);
if(code >= 270 && code <= 289) return parseInt(value);
if(code >= 290 && code <= 299) return parseBoolean(value);
if(code >= 110 && code <= 149) return parseFloat(value.trim());
if(code >= 160 && code <= 179) return parseInt(value.trim());
if(code >= 210 && code <= 239) return parseFloat(value.trim());
if(code >= 270 && code <= 289) return parseInt(value.trim());
if(code >= 290 && code <= 299) return parseBoolean(value.trim());
if(code >= 300 && code <= 369) return value;
if(code >= 370 && code <= 389) return parseInt(value);
if(code >= 370 && code <= 389) return parseInt(value.trim());
if(code >= 390 && code <= 399) return value;
if(code >= 400 && code <= 409) return parseInt(value);
if(code >= 400 && code <= 409) return parseInt(value.trim());
if(code >= 410 && code <= 419) return value;
if(code >= 420 && code <= 429) return parseInt(value);
if(code >= 420 && code <= 429) return parseInt(value.trim());
if(code >= 430 && code <= 439) return value;
if(code >= 440 && code <= 459) return parseInt(value);
if(code >= 460 && code <= 469) return parseFloat(value);
if(code >= 440 && code <= 459) return parseInt(value.trim());
if(code >= 460 && code <= 469) return parseFloat(value.trim());
if(code >= 470 && code <= 481) return value;
if(code === 999) return value;
if(code >= 1000 && code <= 1009) return value;
if(code >= 1010 && code <= 1059) return parseFloat(value);
if(code >= 1060 && code <= 1071) return parseInt(value);
if(code >= 1010 && code <= 1059) return parseFloat(value.trim());
if(code >= 1060 && code <= 1071) return parseInt(value.trim());

@@ -133,0 +133,0 @@ console.log('WARNING: Group code does not have a defined type: %j', { code: code, value: value });

@@ -76,2 +76,5 @@

break;
case 101:
helpers.skipEmbeddedObject(scanner);
break;
case 210:

@@ -78,0 +81,0 @@ entity.extrusionDirection = helpers.parsePoint(scanner);

@@ -16,4 +16,2 @@

case 3:
entity.text ? entity.text += curr.value : entity.text = curr.value;
break;
case 1:

@@ -25,2 +23,5 @@ entity.text ? entity.text += curr.value : entity.text = curr.value;

break;
case 11:
entity.direction = helpers.parsePoint(scanner);
break;
case 40:

@@ -33,2 +34,5 @@ //Note: this is the text height

break;
case 44:
entity.lineSpacing = curr.value;
break;
case 50:

@@ -43,2 +47,5 @@ entity.rotation = curr.value;

break;
case 101:
helpers.skipEmbeddedObject(scanner);
break;
default:

@@ -45,0 +52,0 @@ helpers.checkCommonEntityProperties(entity, curr);

@@ -46,2 +46,20 @@ import AUTO_CAD_COLOR_INDEX from './AutoCadColorIndex';

/** Some entities may contain embedded object which is started by group 101. All the rest data until
* end of entity should not be interpreted as entity attributes. There is no documentation for this
* feature.
* @param scanner
*/
export function skipEmbeddedObject(scanner) {
/* Ensure proper start group. */
scanner.rewind()
let curr = scanner.next()
if (curr.code !== 101) {
throw new Error("Bad call for skipEmbeddedObject()")
}
do {
curr = scanner.next()
} while (curr.code !== 0)
scanner.rewind()
}
/**

@@ -48,0 +66,0 @@ * Attempts to parse codes common to all entities. Returns true if the group

@@ -5,2 +5,3 @@ import {DxfScene, Entity} from "./DxfScene"

import {Matrix3, Vector2} from "three"
import {MTextFormatParser} from "./MTextFormatParser";

@@ -103,3 +104,3 @@ /**

* @param layer {?string}
* @param size {number}
* @param fontSize {number}
* @return {Generator<Entity>} Rendering entities. Currently just indexed triangles for each

@@ -109,4 +110,4 @@ * glyph.

*Render({text, startPos, endPos, rotation = 0, widthFactor = 1, hAlign = 0, vAlign = 0,
color, layer = null, size}) {
const block = new TextBlock(size)
color, layer = null, fontSize}) {
const block = new TextBlock(fontSize)
for (const char of text) {

@@ -122,2 +123,25 @@ const shape = this._GetCharShape(char)

/**
* @param {MTextFormatEntity[]} formattedText Parsed formatted text.
* @param {{x, y}} position Insertion position.
* @param {Number} fontSize
* @param {?Number} width Text block width, no wrapping if undefined.
* @param {?Number} rotation Text block rotation in degrees.
* @param {?{x, y}} direction Text block orientation defined as direction vector. Takes a
* precedence over rotation if both provided.
* @param {number} attachment Attachment point, one of MTextAttachment values.
* @param {?number} lineSpacing Line spacing ratio relative to default one (5/3 of font size).
* @param {number} color
* @param {?string} layer
* @return {Generator<Entity>} Rendering entities. Currently just indexed triangles for each
* glyph.
*/
*RenderMText({formattedText, position, fontSize, width = null, rotation = 0, direction = null,
attachment, lineSpacing = 1, color, layer = null}) {
const box = new TextBox(fontSize, this._GetCharShape.bind(this))
box.FeedText(formattedText)
yield* box.Render(position, width, rotation, direction, attachment, lineSpacing, color,
layer)
}
/** @return {CharShape} Shape for the specified character.

@@ -266,6 +290,4 @@ * Each shape is indexed triangles mesh for font size 1. They should be further transformed as

}
let path = null
let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2
const scale = this.scale
path = new ShapePath()
const path = new ShapePath()
for (const cmd of glyph.path.commands) {

@@ -335,7 +357,460 @@ switch (cmd.type) {

/** Encapsulates calculations for a text block. */
//XXX multiline text
/** MTEXT group attribute 71 values. */
const MTextAttachment = Object.freeze({
TOP_LEFT: 1,
TOP_CENTER: 2,
TOP_RIGHT: 3,
MIDDLE_LEFT: 4,
MIDDLE_CENTER: 5,
MIDDLE_RIGHT: 6,
BOTTOM_LEFT: 7,
BOTTOM_CENTER: 8,
BOTTOM_RIGHT: 9
})
/** Encapsulates layout calculations for a multiline-line text block. */
class TextBox {
/**
* @param fontSize
* @param {Function<CharShape, String>} charShapeProvider
*/
constructor(fontSize, charShapeProvider) {
this.fontSize = fontSize
this.charShapeProvider = charShapeProvider
this.curParagraph = new TextBox.Paragraph(this)
this.paragraphs = [this.curParagraph]
this.spaceShape = charShapeProvider(" ")
}
/** Add some formatted text to the box.
* @param {MTextFormatEntity[]} formattedText Parsed formatted text.
*/
FeedText(formattedText) {
/* For now advanced formatting is not implemented so scopes are just flattened. */
function *FlattenItems(items) {
for (const item of items) {
if (item.type === MTextFormatParser.EntityType.SCOPE) {
yield *FlattenItems(item.content)
} else {
yield item
}
}
}
/* Null is default alignment which depends on attachment point. */
let curAlignment = null
for (const item of FlattenItems(formattedText)) {
switch(item.type) {
case MTextFormatParser.EntityType.TEXT:
for (const c of item.content) {
if (c === " ") {
this.curParagraph.FeedSpace()
} else {
this.curParagraph.FeedChar(c)
}
}
break
case MTextFormatParser.EntityType.PARAGRAPH:
this.curParagraph = new TextBox.Paragraph(this)
this.curParagraph.SetAlignment(curAlignment)
this.paragraphs.push(this.curParagraph)
break
case MTextFormatParser.EntityType.NON_BREAKING_SPACE:
this.curParagraph.FeedChar(" ")
break
case MTextFormatParser.EntityType.PARAGRAPH_ALIGNMENT:
let a = null
switch (item.alignment) {
case "l":
a = TextBox.Paragraph.Alignment.LEFT
break
case "c":
a = TextBox.Paragraph.Alignment.CENTER
break
case "r":
a = TextBox.Paragraph.Alignment.RIGHT
break
case "d":
a = TextBox.Paragraph.Alignment.JUSTIFY
break
case "j":
a = null
break
}
this.curParagraph.SetAlignment(a)
curAlignment = a
break
}
}
}
*Render(position, width, rotation, direction, attachment, lineSpacing, color, layer) {
for (const p of this.paragraphs) {
p.BuildLines(width)
}
if (width === null || width === 0) {
/* Find maximal paragraph width which will define overall box width. */
width = 0
for (const p of this.paragraphs) {
const pWidth = p.GetMaxLineWidth()
if (pWidth > width) {
width = pWidth
}
}
}
let defaultAlignment = TextBox.Paragraph.Alignment.LEFT
switch (attachment) {
case MTextAttachment.TOP_CENTER:
case MTextAttachment.MIDDLE_CENTER:
case MTextAttachment.BOTTOM_CENTER:
defaultAlignment = TextBox.Paragraph.Alignment.CENTER
break
case MTextAttachment.TOP_RIGHT:
case MTextAttachment.MIDDLE_RIGHT:
case MTextAttachment.BOTTOM_RIGHT:
defaultAlignment = TextBox.Paragraph.Alignment.RIGHT
break
}
for (const p of this.paragraphs) {
p.ApplyAlignment(width, defaultAlignment)
}
/* Box local coordinates have top-left corner origin, so Y values are negative. The
* specified attachment should be used to obtain attachment point offset relatively to box
* CS origin.
*/
if (direction !== null) {
/* Direction takes precedence over rotation if specified. */
rotation = Math.atan2(direction.y, direction.x) * 180 / Math.PI
}
const lineHeight = lineSpacing * 5 * this.fontSize / 3
let height = 0
for (const p of this.paragraphs) {
if (p.lines === null) {
/* Paragraph always occupies at least one line. */
height++
} else {
height += p.lines.length
}
}
height *= lineHeight
let origin = new Vector2()
switch (attachment) {
case MTextAttachment.TOP_LEFT:
break
case MTextAttachment.TOP_CENTER:
origin.x = width / 2
break
case MTextAttachment.TOP_RIGHT:
origin.x = width
break
case MTextAttachment.MIDDLE_LEFT:
origin.y = -height / 2
break
case MTextAttachment.MIDDLE_CENTER:
origin.x = width / 2
origin.y = -height / 2
break
case MTextAttachment.MIDDLE_RIGHT:
origin.x = width
origin.y = -height / 2
break
case MTextAttachment.BOTTOM_LEFT:
origin.y = -height
break
case MTextAttachment.BOTTOM_CENTER:
origin.x = width / 2
origin.y = -height
break
case MTextAttachment.BOTTOM_RIGHT:
origin.x = width
origin.y = -height
break
default:
throw new Error("Unhandled alignment")
}
/* Transform for each chunk insertion point. */
const transform = new Matrix3().translate(-origin.x, -origin.y)
.rotate(-rotation * Math.PI / 180).translate(position.x, position.y)
let y = -this.fontSize
for (const p of this.paragraphs) {
if (p.lines === null) {
y -= lineHeight
continue
}
for (const line of p.lines) {
for (let chunkIdx = line.startChunkIdx;
chunkIdx < line.startChunkIdx + line.numChunks;
chunkIdx++) {
const chunk = p.chunks[chunkIdx]
let x = chunk.position
/* First chunk of continuation line never prepended by whitespace. */
if (chunkIdx === 0 || chunkIdx !== line.startChunkIdx) {
x += chunk.GetSpacingWidth()
}
const v = new Vector2(x, y)
v.applyMatrix3(transform)
if (chunk.block) {
yield* chunk.block.Render(v, null, rotation, null,
HAlign.LEFT, VAlign.BASELINE,
color, layer)
}
}
y -= lineHeight
}
}
}
}
TextBox.Paragraph = class {
constructor(textBox) {
this.textBox = textBox
this.chunks = []
this.curChunk = null
this.alignment = null
this.lines = null
}
/** Feed character for current chunk. Spaces should be fed by FeedSpace() method. If space
* character is fed into this method, it is interpreted as non-breaking space.
*/
FeedChar(c) {
const shape = this.textBox.charShapeProvider(c)
if (shape === null) {
return
}
if (this.curChunk === null) {
this._AddChunk()
}
this.curChunk.PushChar(c, shape)
}
FeedSpace() {
if (this.curChunk === null || this.curChunk.lastChar !== null) {
this._AddChunk()
}
this.curChunk.PushSpace()
}
SetAlignment(alignment) {
this.alignment = alignment
}
/** Group chunks into lines.
*
* @param {?number} boxWidth Box width. Do not wrap lines if null (one line is created).
*/
BuildLines(boxWidth) {
if (this.curChunk === null) {
return
}
this.lines = []
let startChunkIdx = 0
let curChunkIdx = 0
let curWidth = 0
const CommitLine = () => {
this.lines.push(new TextBox.Paragraph.Line(this,
startChunkIdx,
curChunkIdx - startChunkIdx,
curWidth))
startChunkIdx = curChunkIdx
curWidth = 0
}
for (; curChunkIdx < this.chunks.length; curChunkIdx++) {
const chunk = this.chunks[curChunkIdx]
const chunkWidth = chunk.GetWidth(startChunkIdx === 0 || curChunkIdx !== startChunkIdx)
if (boxWidth !== null && boxWidth !== 0 && curWidth !== 0 &&
curWidth + chunkWidth > boxWidth) {
CommitLine()
}
chunk.position = curWidth
curWidth += chunkWidth
}
if (startChunkIdx !== curChunkIdx && curWidth !== 0) {
CommitLine()
}
}
GetMaxLineWidth() {
if (this.lines === null) {
return 0
}
let maxWidth = 0
for (const line of this.lines) {
if (line.width > maxWidth) {
maxWidth = line.width
}
}
return maxWidth
}
ApplyAlignment(boxWidth, defaultAlignment) {
if (this.lines) {
for (const line of this.lines) {
line.ApplyAlignment(boxWidth, defaultAlignment)
}
}
}
_AddChunk() {
this.curChunk = new TextBox.Paragraph.Chunk(this, this.textBox.fontSize, this.curChunk)
this.chunks.push(this.curChunk)
}
}
TextBox.Paragraph.Alignment = Object.freeze({
LEFT: 0,
CENTER: 1,
RIGHT: 2,
JUSTIFY: 3
})
TextBox.Paragraph.Chunk = class {
/**
* @param {TextBox.Paragraph} paragraph
* @param {number} fontSize
* @param {?TextBox.Paragraph.Chunk} prevChunk
*/
constructor(paragraph, fontSize, prevChunk) {
this.paragraph = paragraph
this.fontSize = fontSize
this.prevChunk = prevChunk
this.lastChar = null
this.lastShape = null
this.leadingSpaces = 0
this.spaceStartKerning = null
this.spaceEndKerning = null
this.block = null
this.position = null
}
PushSpace() {
if (this.block) {
throw new Error("Illegal operation")
}
this.leadingSpaces++
}
/**
* @param char {string}
* @param shape {CharShape}
*/
PushChar(char, shape) {
if (this.spaceStartKerning === null) {
if (this.leadingSpaces === 0) {
this.spaceStartKerning = 0
this.spaceEndKerning = 0
} else {
if (this.prevChunk && this.prevChunk.lastShape &&
this.prevChunk.fontSize === this.fontSize &&
this.prevChunk.lastShape.font === this.paragraph.textBox.spaceShape.font) {
this.spaceStartKerning =
this.prevChunk.lastShape.font.GetKerning(this.prevChunk.lastChar, " ")
} else {
this.spaceStartKerning = 0
}
if (shape.font === this.paragraph.textBox.spaceShape.font) {
this.spaceEndKerning = shape.font.GetKerning(" ", char)
} else {
this.spaceEndKerning = 0
}
}
}
if (this.block === null) {
this.block = new TextBlock(this.fontSize)
}
this.block.PushChar(char, shape)
this.lastChar = char
this.lastShape = shape
}
GetSpacingWidth() {
return (this.leadingSpaces * this.paragraph.textBox.spaceShape.advance +
this.spaceStartKerning + this.spaceEndKerning) * this.fontSize
}
GetWidth(withSpacing) {
if (this.block === null) {
return 0
}
let width = this.block.GetCurrentPosition()
if (withSpacing) {
width += this.GetSpacingWidth()
}
return width
}
}
TextBox.Paragraph.Line = class {
constructor(paragraph, startChunkIdx, numChunks, width) {
this.paragraph = paragraph
this.startChunkIdx = startChunkIdx
this.numChunks = numChunks
this.width = width
}
ApplyAlignment(boxWidth, defaultAlignment) {
let alignment = this.paragraph.alignment ?? defaultAlignment
switch (alignment) {
case TextBox.Paragraph.Alignment.LEFT:
break
case TextBox.Paragraph.Alignment.CENTER: {
const offset = (boxWidth - this.width) / 2
this.ForEachChunk(chunk => chunk.position += offset)
break
}
case TextBox.Paragraph.Alignment.RIGHT: {
const offset = boxWidth - this.width
this.ForEachChunk(chunk => chunk.position += offset)
break
}
case TextBox.Paragraph.Alignment.JUSTIFY: {
const space = boxWidth - this.width
if (space <= 0 || this.numChunks === 1) {
break
}
const step = space / (this.numChunks - 1)
let offset = 0
this.ForEachChunk(chunk => {
chunk.position += offset
offset += step
})
break
}
default:
throw new Error("Unhandled alignment: " + this.paragraph.alignment)
}
}
ForEachChunk(handler) {
for (let i = 0; i < this.numChunks; i++) {
handler(this.paragraph.chunks[this.startChunkIdx + i])
}
}
}
/** Encapsulates calculations for a single-line text block. */
class TextBlock {
constructor(size) {
this.size = size
constructor(fontSize) {
this.fontSize = fontSize
/* Element is {shape: CharShape, vertices: ?{Vector2}[]} */

@@ -363,10 +838,10 @@ this.glyphs = []

}
const x = this.curX + offset * this.size
const x = this.curX + offset * this.fontSize
let vertices
if (shape.vertices) {
vertices = shape.GetVertices({x, y: 0}, this.size)
const xMin = x + shape.bounds.xMin * this.size
const xMax = x + shape.bounds.xMax * this.size
const yMin = shape.bounds.yMin * this.size
const yMax = shape.bounds.yMax * this.size
vertices = shape.GetVertices({x, y: 0}, this.fontSize)
const xMin = x + shape.bounds.xMin * this.fontSize
const xMax = x + shape.bounds.xMax * this.fontSize
const yMin = shape.bounds.yMin * this.fontSize
const yMax = shape.bounds.yMax * this.fontSize
/* Leading/trailing spaces not accounted intentionally now. */

@@ -392,3 +867,3 @@ if (this.bounds === null) {

}
this.curX = x + shape.advance * this.size
this.curX = x + shape.advance * this.fontSize
this.glyphs.push({shape, vertices})

@@ -399,2 +874,6 @@ this.prevChar = char

GetCurrentPosition() {
return this.curX
}
/**

@@ -401,0 +880,0 @@ * @param startPos {{x,y}} TEXT group first alignment point.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc